diff --git a/README.md b/README.md index c9f433c3..5c5dadb7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This project implements a JMeter plugin to **support RTE (Remote Terminal Emulation) protocols** by providing a recorder for automatic test plan creation, and config and sampler for protocol interactions. -Nowadays the plugin supports **IBM protocol's TN5250, TN3270 and VT420** by using embedded [xtn5250](https://sourceforge.net/projects/xtn5250/), [dm3270](http://dmolony.github.io/) and [jvt220](https://github.com/jawi/jVT220) emulators with modifications [xtn5250 fork](https://github.com/abstracta/xtn5250), [dm3270 fork](https://github.com/abstracta/dm3270) and [jvt220 fork](https://github.com/Blazemeter/jVT220) to better accommodate to the plugin usage (exception handling, logging, external dependencies, etc). +Nowadays the plugin supports **IBM protocol's TN5250, TN3270 and VT420** by using embedded [xtn5250](https://sourceforge.net/projects/xtn5250/), [dm3270](http://dmolony.github.io/) and [jvt220](https://github.com/jawi/jVT220) emulators, with some modifications on [xtn5250 fork](https://github.com/abstracta/xtn5250), [dm3270 fork](https://github.com/abstracta/dm3270) and [jvt220 fork](https://github.com/Blazemeter/jVT220) to better accommodate to the plugin usage (exception handling, logging, external dependencies, etc). People who usually work with these IBM servers interact with them, basically, by sending keystrokes from the terminal keyboard (or emulator) to fill forms or call processes. The plugin provides a [recording controller](#a-recording-controller-rte-recorder), which allows the user to interact through a terminal emulator, recording every interaction (samplers) with the mainframe application. Additionally, the plugin allows for manual test plan creation, providing a config element for setting connection parameters and a sampler to set fields on screen and attention key to send to the mainframe application. Besides, the sampler allows to simulate the existing attention keys on the terminal keyboard like ENTER, F1, F2, F3..., ATTN, CLEAR, etc. @@ -83,12 +83,28 @@ The RTE Sampler fields are: - *Disconnect*. This option allows to explicitly close the connection to the terminal server. This allows to restart the emulator status by re connecting to the server en following samplers. > As previously stated, connections are (by default) automatically closed at the end of each thread iteration, so is not required to add a sampler with disconnect action at the end of each thread loop iteration. - *RTE Message*. When "Send keys" action is selected it is possible to specify fields to send and attention key to use: - - *Payload*. Contains a grid where the user can add different types of input. - - Input by Tabulator: It will make as many tabulations as specified to the mainframe, before - sending the _"value"_. It is useful to avoid using coordinates. - - Input by Label: It precedes from a terminal screen label. It will send to the mainframe application the already set value to a field which matches the selected label. - - Input by Coordinates (row and column): It will send the text introduced in _"value"_ to the mainframe application at that position. - > For more information about inputs and how they work in a normal flow, check this [examples](docs/recorder/terminal-emulator/terminal-emulator.md#Input By Label Usage). + - *Payload*. Contains a grid in which user can specify different types of inputs: + - **Input by Coordinates:** (row and column) of a field in the screen. Rows and columns starting from [1,1] (are 1 indexed). + - **Input by Label:** It could be a word or a text preceded by a field on the terminal screen. + - **Input by Navigation:** As the name describes, this input is going to navigate before placing the input value (String). There are five types of navigation; the four arrow navigation keys, and the tabulator key. Also you can specify how many times to send the navigation key before the input value. + > Regardless from the input type, all of them will send a value (String) to the mainframe application. + - **Copy from Clipboard:** In order to make the input creation quicker, a string convention as been added. Remember to always make a tabulation between prefixes or inputs. + ```text + input + input + input + input + input + 1 2 input + UserID input + ``` + + - ***\:*** It will create an input by navigation with just one tabulator key before sending the input value. + - ***\:*** It will create an up arrow navigation input, sending four times the up arrow before sending the input value. + - ***\\:*** It will create a left arrow navigation with a double repetition of the left key before sending the input value. Which is equivalent to **. + - ***1 2*** It will create a coordinate input with row 1 and column 2 with an input value equals to 'input' as we see in above representation. + - ***UserID*** This format will create a label input, with the label 'UserID' and the input value equals to 'input'. + - *Attention Keys*. These buttons trigger the attention keys to be sent to the server on each sample. They all represent a key from a terminal's keyboard. - *Wait for*. When using "Connect" or "Send keys" action it is possible to wait for a specific condition. If this condition is not reached after a specific time (defined in *Timeout* value), the sampler returns timeout error. There are four defined waiters: - *Sync*. Waits for the system to return from X SYSTEM or Input Inhibited mode. Default value is checked, as it's recommended to always check that the system is not in Input Inhibited Mode after a sample (and before the next one) in order to get the correct screen in the sample result (and to ensure that the next sampler is executed from the desired screen). On the other hand, the sampler does an implicit "Wait for sync" each time it connects to a server, which means that if *Connect* mode is used, then it's not needed to check the *Wait for sync* function, unless you want to change the default timeout. @@ -100,10 +116,18 @@ The RTE Sampler fields are: All the "waiters" use a stable timeout value (in milliseconds) which specifies the time to wait for the emulator to remain at the desired state. The default value is 1000 milliseconds, but can be changed by adding the property `RTEConnectionConfig.stableTimeoutMillis=` in *jmeter.properties* file. The "Wait for silent" waiter is not affected by this setting since it has an explicit field for such purpose. > Warning: both Stable Timeout and Silent Interval should be shorter than Timeout value, otherwise the sampler will always return a timeout error. -#### RTE-Extractor -![alt_text](docs/extractor/rte-extractor.png) +##### Character timeout +With the addition of the VT420 protocol, now we also support its "character at time" behavior, which means that, every time we type a key, we have to wait for a response of the server to send the next one. +Therefore a character timeout comes to play. This period of time is the maximum amount in milliseconds to wait for a server response when sending a character. -RTE-Extractor is a post-processor which its main purpose is to extract positions from response headers to be used later as a JMeter variable. +> Notice: If the time elapses, the user will experiment with a TimeoutException. + +The default value of this timeout is 60000 milliseconds, but can be changed by adding the property `RTEConnectionConfig.characterTimeoutMillis=` in *jmeter.properties* file. + +#### RTE-Position Extractor +![alt_text](docs/extractor/rte-position-extractor.png) + +RTE-Position Extractor is a post-processor which its main purpose is to extract positions from response headers to be used later as a JMeter variable. > Check [here](/docs/extractor/rte-extractor.md) for more information. @@ -139,6 +163,50 @@ As explained previously, the RTE Sampler has 4 types of waiters which work as sy - *Wait for Silent*: The client is considered to be silent when the terminal does not receive any characters from the server so, by setting the proper silent interval, the user could ensure that the server has sent all the information available to the client before continue the execution. - *Wait for Text*: This waiter could be useful to check for a specific message before continue the execution. For example, it could be used to wait for a message with the confirmation that a specific process ended correctly or to check if a search returned any result before continue. +## Tips + +#### How to extract a label from the screen. +The RTE plugin does not have a functionality for itself to achieve this goal. Therefore, Regular Expression Extractor will be our solution. + +Example of usage: + + ![alter_text](docs/extractor/regex-extractor-date-screen.png) + +We have obtained the screen above from a sampler response. Suppose we want to extract the date which appears close to the right top corner "**123456**". + +In order to capture the date value we have to add a Regular Expression Extractor embedded in **bzm-RTE-SEND_INPUT-1** sampler. + +A proper configuration in that case would be something like this: + ![alter_text](docs/extractor/regex-extractor-label-config.png) + > If you want to understand how Regular Expression Extractor works, visit [this](https://guide.blazemeter.com/hc/en-us/articles/207421325-Using-RegEx-Regular-Expression-Extractor-with-JMeter-Using-RegEx-(Regular-Expression-Extractor)-with-JMeter) JMeter page. + + +#### How to extract a value on the screen from a given position. + +In order to get a value, we have to know the beginning of the value in coordinates and the length of the text to match. + +![alter_text](docs/extractor/regex-extractor-date-screen.png) + +For instance, suppose that we need to extract the date using positions. We already know the size of the screen, which is 24x80 (in this case). + +Using the emulator we have identify the beginning coordinates of the date value *(1,67)*. Also we know that the length of the text would be six characters. + +With all this information we have develop a regex to match that criteria: + +***^(.+\n){0}.{67}((.){6})(.\*\n){23}(.\*)$*** + + - **(.+\n){0}**: The _0_ parameter is the row of the beginning coordinates (1) minus one. + - **.{67}**: The _67_ parameter is the column of the beginning coordinates. + - **((.){6})**: The _6_ parameter is the length of the text we want to match. Also this group is the important one, match group NÂș2, which is the Template value in Regular Expression Extractor. + - **(.\*\n){23}(.\*)**: The _23_ parameter is the height of the screen (24) minus our position row (1). + +Once we got the regex, our Regular Expression Extractor would be like this: + +![alter_text](docs/extractor/regex-extractor-position-config.png) + > If you want to understand how Regular Expression Extractor works, visit [this](https://guide.blazemeter.com/hc/en-us/articles/207421325-Using-RegEx-Regular-Expression-Extractor-with-JMeter-Using-RegEx-(Regular-Expression-Extractor)-with-JMeter) JMeter page. + + + ## Compatibility diff --git a/docs/extractor/extractor-component.gif b/docs/extractor/extractor-component.gif deleted file mode 100644 index 75fb4207..00000000 Binary files a/docs/extractor/extractor-component.gif and /dev/null differ diff --git a/docs/extractor/position-extractor-component.gif b/docs/extractor/position-extractor-component.gif new file mode 100644 index 00000000..65efa058 Binary files /dev/null and b/docs/extractor/position-extractor-component.gif differ diff --git a/docs/extractor/position-extractor-usage.gif b/docs/extractor/position-extractor-usage.gif new file mode 100644 index 00000000..2cdf52ec Binary files /dev/null and b/docs/extractor/position-extractor-usage.gif differ diff --git a/docs/extractor/regex-extractor-date-screen.png b/docs/extractor/regex-extractor-date-screen.png new file mode 100644 index 00000000..a74c443b Binary files /dev/null and b/docs/extractor/regex-extractor-date-screen.png differ diff --git a/docs/extractor/regex-extractor-label-config.png b/docs/extractor/regex-extractor-label-config.png new file mode 100644 index 00000000..2fa04d1b Binary files /dev/null and b/docs/extractor/regex-extractor-label-config.png differ diff --git a/docs/extractor/regex-extractor-position-config.png b/docs/extractor/regex-extractor-position-config.png new file mode 100644 index 00000000..5bdbe765 Binary files /dev/null and b/docs/extractor/regex-extractor-position-config.png differ diff --git a/docs/extractor/rte-extractor.md b/docs/extractor/rte-extractor.md index d3d5720f..dd8793be 100644 --- a/docs/extractor/rte-extractor.md +++ b/docs/extractor/rte-extractor.md @@ -1,12 +1,13 @@ -# RTE-Extractor -![alt_text](rte-extractor.png) +# RTE-Position-Extractor +![alt_text](rte-position-extractor.png) **REMEMBER**: This Extractor will ease the development of future implementations. Therefore is not mandatory for script creation. However it can be used in particular cases. The RTE Extractor is a component that must be embedded into a sampler. -
How to add a RTE-Extractor:
-![alt_text](extractor-component.gif) +
How to add a RTE-Position Extractor:
+ +![alt_text](position-extractor-component.gif) The aim of the Extractor is to have the ability to look into response headers for a Field Position (from a given position) and to set a JMeter Variable with the corresponding field position. @@ -44,6 +45,6 @@ In this example we will look for the next field from position (1,2). As we have Let's visualize all of this: -![alterText](extractor-usage.gif) +![alterText](position-extractor-usage.gif) > In this example we gave the extractor the beginning of a field (1,2), and as you could see, it will search for the next field on the right, even when the given position is inside a field or outside of it. \ No newline at end of file diff --git a/docs/extractor/rte-extractor.png b/docs/extractor/rte-extractor.png deleted file mode 100644 index 1f704313..00000000 Binary files a/docs/extractor/rte-extractor.png and /dev/null differ diff --git a/docs/extractor/rte-position-extractor.png b/docs/extractor/rte-position-extractor.png new file mode 100644 index 00000000..3762abaf Binary files /dev/null and b/docs/extractor/rte-position-extractor.png differ diff --git a/docs/extractor/variable-prefix.png b/docs/extractor/variable-prefix.png index b97e781f..de8c0aec 100644 Binary files a/docs/extractor/variable-prefix.png and b/docs/extractor/variable-prefix.png differ diff --git a/docs/recorder/terminal-emulator/terminal-emulator.md b/docs/recorder/terminal-emulator/terminal-emulator.md index c34aba17..337d737d 100644 --- a/docs/recorder/terminal-emulator/terminal-emulator.md +++ b/docs/recorder/terminal-emulator/terminal-emulator.md @@ -3,6 +3,7 @@ - Pressing ![alt_text](/src/main/resources/dark-theme/copy.png) you are able to copy from the emulator, also using the standard keyboard shortcuts. - Pressing ![alt_text](/src/main/resources/dark-theme/paste.png) you are able to paste in cursor position on the emulator, also using standard shortcuts. + > ANSI sequences are not allowed when pasting. E.g: '\t' '\033[A'. - You can select a screen area to be used as input field label, press ![alt_text](/src/main/resources/dark-theme/inputByLabel.png) and then set the input field text, to record a test plan that uses the provided label to locate the input field on the screen and fill the field with provided text. > Input by label allows to find the field on the screen regardless of changes of field positioning, which makes recorded test plans more robust (than using default input by coord). @@ -13,12 +14,16 @@ - You can press assertion button ![alt_text](/src/main/resources/dark-theme/assertion.png) when you want to make sure that a part of the screen has appeared in the screen. This assertion has the same behaviour as JMeter Assertions. To assert for a part of the screen you just have to select a part of the screen and press the button. An assertion will be added to corresponding sampler. >[Here](#recorder-screen-assertion-usage) is an example of usage. - + +- If you see ![alt_text](/src/main/resources/dark-theme/blocked-cursor.png) it is because you are + using the emulator with a VT protocol which it does not support the functionality of moving the + cursor position by clicking on the emulator. + - Clicking on ![alter_text](/src/main/resources/dark-theme/not-visible-credentials.png) / ![alter_text](/src/main/resources/dark-theme/visible-credentials.png) you will be able to show/hide credentials. - If you click on the ![alter_text](/src/main/resources/dark-theme/help.png) icon in the emulator, a pop up window will be displayed with general help information on the emulator: shortcuts, explanation about indicators on the screen, etc. -- **Sample name:** As the label says, you can specify the name of the sample in current screen. +- **Sample name:** As the label says, you can specify the name of the sample in current screen. ### Input By Label Usage diff --git a/docs/rte-sampler.png b/docs/rte-sampler.png index 656829c1..cbb8c38a 100644 Binary files a/docs/rte-sampler.png and b/docs/rte-sampler.png differ diff --git a/pom.xml b/pom.xml index d5a37a97..392cf235 100644 --- a/pom.xml +++ b/pom.xml @@ -6,14 +6,14 @@ com.blazemeter.jmeter jmeter-bzm-rte jar - 2.3 + 3.0 RTEPlugin Sampler as JMeter plugin UTF-8 UTF-8 - 3.1 + 3.2 @@ -35,12 +35,17 @@ com.github.blazemeter xtn5250 - 3.2 + 3.2.1 com.github.blazemeter dm3270 - 0.12.1-lib + 0.12.3-lib + + + com.github.blazemeter + jVT220 + jvt220-v1.3 @@ -230,7 +235,7 @@ - wiresham,guava,snakeyaml,jackson-databind,jackson-core,jackson-annotations,xtn5250,dm3270,pcap4j-core + wiresham,guava,snakeyaml,jackson-databind,jackson-core,jackson-annotations,xtn5250,dm3270,pcap4j-core,jVT220 ${project.build.directory}/jmeter-test/lib true diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/BaseProtocolClient.java b/src/main/java/com/blazemeter/jmeter/rte/core/BaseProtocolClient.java index b8007dc9..f013501c 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/core/BaseProtocolClient.java +++ b/src/main/java/com/blazemeter/jmeter/rte/core/BaseProtocolClient.java @@ -11,6 +11,7 @@ import java.security.GeneralSecurityException; import java.util.List; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import javax.net.SocketFactory; @@ -19,8 +20,9 @@ public abstract class BaseProtocolClient implements RteProtocolClient { + protected static final ThreadFactory NAMED_THREAD_FACTORY = (runnable) -> new Thread(runnable, + "STABLE-TIMEOUT-EXECUTOR"); private static final Logger LOG = LoggerFactory.getLogger(BaseProtocolClient.class); - protected ExceptionHandler exceptionHandler; protected ScheduledExecutorService stableTimeoutExecutor; @@ -37,25 +39,25 @@ protected SocketFactory getSocketFactory(SSLType sslType, String server) throws } @Override - public void send(List input, AttentionKey attentionKey) + public void send(List input, AttentionKey attentionKey, long echoTimeoutMillis) throws RteIOException { - input.forEach(this::setField); + input.forEach(i -> setField(i, echoTimeoutMillis)); sendAttentionKey(attentionKey); exceptionHandler.throwAnyPendingError(); } - protected abstract void setField(Input input); + protected abstract void setField(Input input, long echoTimeoutMillis); protected abstract void sendAttentionKey(AttentionKey attentionKey); @Override public void await(List waitConditions) throws InterruptedException, TimeoutException, RteIOException { - List listeners = waitConditions.stream() + List> listeners = waitConditions.stream() .map(this::buildWaiter) .collect(Collectors.toList()); try { - for (ConditionWaiter listener : listeners) { + for (ConditionWaiter listener : listeners) { listener.await(); } } finally { @@ -63,7 +65,7 @@ public void await(List waitConditions) } } - protected abstract ConditionWaiter buildWaiter(WaitCondition waitCondition); + protected abstract ConditionWaiter buildWaiter(WaitCondition waitCondition); @Override public void disconnect() throws RteIOException { diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/CharacterBasedProtocolClient.java b/src/main/java/com/blazemeter/jmeter/rte/core/CharacterBasedProtocolClient.java new file mode 100644 index 00000000..b0a2ff41 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/core/CharacterBasedProtocolClient.java @@ -0,0 +1,12 @@ +package com.blazemeter.jmeter.rte.core; + +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; + +public interface CharacterBasedProtocolClient extends RteProtocolClient { + + void addScreenChangeListener(ScreenChangeListener listener); + + void removeScreenChangeListener(ScreenChangeListener listener); + + void send(String character); +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/NavigationInput.java b/src/main/java/com/blazemeter/jmeter/rte/core/NavigationInput.java new file mode 100644 index 00000000..2a211543 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/core/NavigationInput.java @@ -0,0 +1,138 @@ +package com.blazemeter.jmeter.rte.core; + +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import java.awt.Dimension; +import java.util.Objects; + +public class NavigationInput extends Input { + + private int repeat; + private NavigationType navigationType; + + public NavigationInput(int repeat, NavigationType navigationType, String value) { + super(value); + this.repeat = repeat; + this.navigationType = navigationType; + } + + private NavigationInput(NavigationInputBuilder builder) { + input = builder.input; + setRepeat(builder.repeat); + setNavigationType(builder.navigationType); + } + + public Position calculateInputFinalPosition(Position currentPos, + Dimension screenSize) { + int finalRowPos = currentPos.getRow(); + int finalColPos = currentPos.getColumn(); + switch (navigationType) { + case DOWN: + finalRowPos = Math.floorMod(finalRowPos + repeat, screenSize.height); + break; + case UP: + finalRowPos = finalRowPos - repeat; + if (finalRowPos < 1) { + finalRowPos = finalRowPos == 0 ? screenSize.height + : Math.floorMod(Math.abs(finalRowPos) > screenSize.height ? finalRowPos + : Math.abs(finalRowPos), + screenSize.height); + } + break; + case LEFT: + finalColPos = finalColPos - repeat; + while (finalColPos < 1) { + finalColPos = screenSize.width + finalColPos; + finalRowPos = finalRowPos == 1 ? screenSize.height : --finalRowPos; + } + break; + case RIGHT: + finalColPos = finalColPos + repeat; + while (finalColPos > screenSize.width) { + finalColPos = finalColPos - screenSize.width; + finalRowPos = finalRowPos == screenSize.height ? 1 : ++finalRowPos; + } + break; + default: + throw new UnsupportedOperationException( + "Invalid arrow navigation type (" + navigationType + ")"); + } + return new Position(finalRowPos, finalColPos); + } + + @Override + public String getCsv() { + return "<" + navigationType + "*" + repeat + ">\t" + input; + } + + public int getRepeat() { + return repeat; + } + + public void setRepeat(int repeat) { + this.repeat = repeat; + } + + public NavigationType getNavigationType() { + return navigationType; + } + + public void setNavigationType(NavigationType navigationType) { + this.navigationType = navigationType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NavigationInput that = (NavigationInput) o; + return repeat == that.repeat && + navigationType == that.navigationType; + } + + @Override + public int hashCode() { + return Objects.hash(repeat, navigationType); + } + + @Override + public String toString() { + return getCsv(); + } + + public static final class NavigationInputBuilder { + + private String input; + private int repeat; + private NavigationType navigationType; + + public NavigationInputBuilder() { + } + + public NavigationInputBuilder withInput(String val) { + input = val; + return this; + } + + public NavigationInputBuilder withRepeat(int val) { + repeat = val; + return this; + } + + public NavigationInputBuilder withNavigationType(NavigationType val) { + navigationType = val; + return this; + } + + public NavigationInput build() { + return new NavigationInput(this); + } + + public NavigationType getNavigationType() { + return navigationType; + } + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/Position.java b/src/main/java/com/blazemeter/jmeter/rte/core/Position.java index 37815ae3..ec88818b 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/core/Position.java +++ b/src/main/java/com/blazemeter/jmeter/rte/core/Position.java @@ -9,8 +9,8 @@ public class Position { public static final int UNSPECIFIED_INDEX = 0; + public static final Position DEFAULT_POSITION = new Position(0, 0); private static final Pattern POSITION_PATTERN = Pattern.compile("^\\((\\d+),(\\d+)\\)$"); - private int row; private int column; @@ -23,6 +23,13 @@ public Position(int row, int column) { this.column = column; } + public Position(Position other) { + if (other != null) { + this.row = other.getRow(); + this.column = other.getColumn(); + } + } + public static Position fromString(String text) { Matcher m = POSITION_PATTERN.matcher(text); if (m.matches()) { @@ -75,4 +82,8 @@ public int compare(Object o) { .thenComparing(Position::getColumn) .compare(this, (Position) o); } + + public boolean isConsecutiveWith(Position other) { + return row == other.getRow() && column + 1 == other.getColumn(); + } } diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/Protocol.java b/src/main/java/com/blazemeter/jmeter/rte/core/Protocol.java index 1882bd81..4d67d1db 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/core/Protocol.java +++ b/src/main/java/com/blazemeter/jmeter/rte/core/Protocol.java @@ -2,12 +2,14 @@ import com.blazemeter.jmeter.rte.protocols.tn3270.Tn3270Client; import com.blazemeter.jmeter.rte.protocols.tn5250.Tn5250Client; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; import java.util.function.Supplier; public enum Protocol { TN5250(Tn5250Client::new), - TN3270(Tn3270Client::new); - + TN3270(Tn3270Client::new), + VT420(Vt420Client::new); + private final Supplier factory; Protocol(Supplier s) { diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/RteProtocolClient.java b/src/main/java/com/blazemeter/jmeter/rte/core/RteProtocolClient.java index 9d304d72..bfff7e72 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/core/RteProtocolClient.java +++ b/src/main/java/com/blazemeter/jmeter/rte/core/RteProtocolClient.java @@ -40,12 +40,12 @@ void await(List waitConditions) void removeTerminalStateListener(TerminalStateListener terminalStateListener); - void send(List input, AttentionKey attentionKey) + void send(List input, AttentionKey attentionKey, long echoTimeoutMillis) throws RteIOException; Screen getScreen(); - boolean isInputInhibited(); + Optional isInputInhibited(); Optional getCursorPosition(); diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilder.java b/src/main/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilder.java index afd3ed9e..4382f967 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilder.java +++ b/src/main/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilder.java @@ -99,7 +99,7 @@ public RteSampleResultBuilder withSuccessResponse(RteProtocolClient client) { private void updateResponseFromClient(RteProtocolClient client) { cursorPosition = client.getCursorPosition().orElse(null); soundedAlarm = client.isAlarmOn(); - inputInhibitedResponse = client.isInputInhibited(); + inputInhibitedResponse = client.isInputInhibited().orElse(false); screen = client.getScreen(); } diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/Screen.java b/src/main/java/com/blazemeter/jmeter/rte/core/Screen.java index 35428d05..360e6df5 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/core/Screen.java +++ b/src/main/java/com/blazemeter/jmeter/rte/core/Screen.java @@ -32,6 +32,13 @@ public Screen(Dimension size) { this.size = size; } + public Screen(Screen other) { + if (other != null) { + segments = new ArrayList<>(other.segments); + size = other.size; + } + } + public static Screen valueOf(String screen) { int width = screen.indexOf('\n'); int height = screen.length() / (width + 1); @@ -44,6 +51,12 @@ public static Screen valueOf(String screen) { return ret; } + public static Screen buildScreenFromText(String screenText, Dimension screenSize) { + Screen scr = new Screen(screenSize); + scr.addSegment(0, screenText.replace("\n", "")); + return scr; + } + public static Screen fromHtml(String html) { try { Document doc = parseHtmlDocument(html); diff --git a/src/main/java/com/blazemeter/jmeter/rte/core/TabulatorInput.java b/src/main/java/com/blazemeter/jmeter/rte/core/TabulatorInput.java deleted file mode 100644 index aaff09fe..00000000 --- a/src/main/java/com/blazemeter/jmeter/rte/core/TabulatorInput.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.blazemeter.jmeter.rte.core; - -import java.util.Objects; - -public class TabulatorInput extends Input { - - //offset is the number of tabs that will be sent - private final int offset; - - public TabulatorInput(int offset, String value) { - super(value); - this.offset = offset; - } - - @Override - public String getCsv() { - return "\t" + getInput(); - } - - @Override - public String toString() { - return offset + " tab/s:" + getInput(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TabulatorInput that = (TabulatorInput) o; - return Objects.equals(offset, that.offset) && - Objects.equals(getInput(), that.getInput()); - } - - public int getOffset() { - return offset; - } - - @Override - public int hashCode() { - return Objects.hash(offset, getInput()); - } -} diff --git a/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorGui.java b/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorGui.java index e1f58fdd..cc5914f8 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorGui.java +++ b/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorGui.java @@ -20,7 +20,7 @@ public RTEExtractorGui() { @Override public String getStaticLabel() { - return "bzm - RTE Extractor"; + return "bzm - RTE Position Extractor"; } @Override diff --git a/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorPanel.java b/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorPanel.java index 6ed94c62..24bacadd 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorPanel.java +++ b/src/main/java/com/blazemeter/jmeter/rte/extractor/RTEExtractorPanel.java @@ -63,7 +63,7 @@ public RTEExtractorPanel() { private void setRadioButtonConfiguration(JPanel fieldPanel) { SwingUtils.setEnabledRecursively(fieldPanel, false); cursorPosition.setText("Extract cursor position"); - nextFieldPosition.setText("Extract next field from position"); + nextFieldPosition.setText("Extract field position from"); cursorPosition.setEnabled(true); cursorPosition.setName("cursorPosition"); nextFieldPosition.setName("nextFieldPosition"); diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270Client.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270Client.java index 673032ed..dfb8a598 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270Client.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270Client.java @@ -5,9 +5,9 @@ import com.blazemeter.jmeter.rte.core.CoordInput; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import com.blazemeter.jmeter.rte.core.Position; import com.blazemeter.jmeter.rte.core.Screen; -import com.blazemeter.jmeter.rte.core.TabulatorInput; import com.blazemeter.jmeter.rte.core.TerminalType; import com.blazemeter.jmeter.rte.core.exceptions.ConnectionClosedException; import com.blazemeter.jmeter.rte.core.exceptions.InvalidFieldLabelException; @@ -30,6 +30,7 @@ import com.blazemeter.jmeter.rte.protocols.tn3270.listeners.Tn3270TerminalStateListenerProxy; import com.blazemeter.jmeter.rte.protocols.tn3270.listeners.UnlockListener; import com.blazemeter.jmeter.rte.protocols.tn3270.listeners.VisibleCursorListener; +import com.blazemeter.jmeter.rte.sampler.NavigationType; import com.bytezone.dm3270.TerminalClient; import com.bytezone.dm3270.application.KeyboardStatusListener; import com.bytezone.dm3270.commands.AIDCommand; @@ -48,6 +49,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; +import javax.naming.OperationNotSupportedException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,7 +123,7 @@ public List getSupportedTerminalTypes() { @Override public void connect(String server, int port, SSLType sslType, TerminalType terminalType, long timeoutMillis) throws RteIOException, InterruptedException, TimeoutException { - stableTimeoutExecutor = Executors.newSingleThreadScheduledExecutor(); + stableTimeoutExecutor = Executors.newSingleThreadScheduledExecutor(NAMED_THREAD_FACTORY); Tn3270TerminalType termType = (Tn3270TerminalType) terminalType; client = new TerminalClient(termType.getModel(), termType.getScreenDimensions()); client.setUsesExtended3270(termType.isExtended()); @@ -184,33 +186,25 @@ public void removeTerminalStateListener(TerminalStateListener listener) { } - private void setFieldByCoord(CoordInput i) { - try { - client.setFieldTextByCoord(i.getPosition().getRow(), - i.getPosition().getColumn(), i.getInput()); - } catch (IllegalArgumentException e) { - throw new InvalidFieldPositionException(i.getPosition(), e); - } - } - @Override - protected void setField(Input i) { + protected void setField(Input i, long echoTimeoutMillis) { if (i instanceof CoordInput) { setFieldByCoord((CoordInput) i); } else if (i instanceof LabelInput) { setFieldByLabel((LabelInput) i); - } else if (i instanceof TabulatorInput) { - setFieldByTabulator((TabulatorInput) i); + } else if (i instanceof NavigationInput) { + setFieldByNavigationType((NavigationInput) i); } else { throw new IllegalArgumentException("Invalid input type: " + i.getClass()); } } - private void setFieldByTabulator(TabulatorInput i) { + private void setFieldByCoord(CoordInput i) { try { - client.setTabulatedInput(i.getInput(), i.getOffset()); - } catch (NoSuchElementException e) { - throw new ScreenWithoutFieldException(); + client.setFieldTextByCoord(i.getPosition().getRow(), + i.getPosition().getColumn(), i.getInput()); + } catch (IllegalArgumentException e) { + throw new InvalidFieldPositionException(i.getPosition(), e); } } @@ -222,6 +216,35 @@ private void setFieldByLabel(LabelInput i) { } } + private void setFieldByNavigationType(NavigationInput i) { + if (i.getNavigationType().equals(NavigationType.TAB)) { + try { + client + .setTabulatedInput(i.getInput(), i.getRepeat()); + } catch (NoSuchElementException | NoSuchFieldException e) { + throw new ScreenWithoutFieldException(); + } + } else { + if (Arrays.asList(NavigationType.values()).contains(i.getNavigationType())) { + Position currentPos = + getCursorPosition().orElseThrow(() -> new NoSuchElementException("No position " + + "available")); + + Position finalPosition = i + .calculateInputFinalPosition(currentPos, + new Dimension(client.getScreenDimensions().rows, + client.getScreenDimensions().columns)); + client + .setFieldTextByCoord(finalPosition.getRow(), finalPosition.getColumn(), + i.getInput()); + } else { + exceptionHandler + .setPendingError(new OperationNotSupportedException( + "The navigation type \'" + i.getNavigationType() + "\' is not supported")); + } + } + } + @Override protected void sendAttentionKey(AttentionKey attentionKey) { Byte actionCommand = AID_COMMANDS.get(attentionKey); @@ -233,7 +256,7 @@ protected void sendAttentionKey(AttentionKey attentionKey) { } @Override - protected ConditionWaiter buildWaiter(WaitCondition waitCondition) { + protected ConditionWaiter buildWaiter(WaitCondition waitCondition) { if (waitCondition instanceof SyncWaitCondition) { return new UnlockListener((SyncWaitCondition) waitCondition, this, stableTimeoutExecutor, exceptionHandler); @@ -262,7 +285,7 @@ public Screen getScreen() { } } - public Screen buildScreenFromText(String screenText) { + private Screen buildScreenFromText(String screenText) { Screen ret = new Screen(getScreenSize()); int lastNonBlankPosition = screenText.length() - 1; while (lastNonBlankPosition >= 0 && (screenText.charAt(lastNonBlankPosition) == ' ' @@ -311,8 +334,8 @@ public Dimension getScreenSize() { } @Override - public boolean isInputInhibited() { - return client == null || client.isKeyboardLocked(); + public Optional isInputInhibited() { + return Optional.of(client == null || client.isKeyboardLocked()); } @Override diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/ScreenTextListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/ScreenTextListener.java index e2c31094..840c0c21 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/ScreenTextListener.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/ScreenTextListener.java @@ -29,7 +29,7 @@ public void keyboardStatusChanged(KeyboardStatusChangedEvent keyboardStatusChang @Override public void cursorMoved(int i, int i1, Field field) { - updateConditionState("cursor moved"); + updateConditionState(CURSOR_MOVED); } diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/SilenceListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/SilenceListener.java index 23885d25..fccc4461 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/SilenceListener.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/SilenceListener.java @@ -29,7 +29,7 @@ public void keyboardStatusChanged(KeyboardStatusChangedEvent keyboardStatusChang @Override public void cursorMoved(int i, int i1, Field field) { - handleReceivedEvent("cursorMoved"); + handleReceivedEvent(CURSOR_MOVED); } @Override @@ -51,7 +51,8 @@ protected boolean getCurrentConditionState() { } private void handleReceivedEvent(String event) { - /*we are updating over here because + /* + we are updating over here because silent does not really have a condition. Then always when some event arrives we need to startStablePeriod again. diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/Tn3270ConditionWaiter.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/Tn3270ConditionWaiter.java index 4c2c8d57..4ed97390 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/Tn3270ConditionWaiter.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/Tn3270ConditionWaiter.java @@ -9,8 +9,9 @@ public abstract class Tn3270ConditionWaiter extends ConditionWaiter { + protected static final String CURSOR_MOVED = "cursor moved"; protected Tn3270Client client; - + public Tn3270ConditionWaiter(T condition, Tn3270Client client, ScheduledExecutorService stableTimeoutExecutor, ExceptionHandler exceptionHandler) { super(condition, stableTimeoutExecutor, exceptionHandler); diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListener.java index a6217e8b..8e1015da 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListener.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListener.java @@ -30,7 +30,7 @@ public void stop() { @Override protected boolean getCurrentConditionState() { - return !client.isInputInhibited(); + return !client.isInputInhibited().get(); } } diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/VisibleCursorListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/VisibleCursorListener.java index f3a751bb..16654ce4 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/VisibleCursorListener.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/VisibleCursorListener.java @@ -23,7 +23,7 @@ private Position getCursorPosition() { @Override public void cursorMoved(int i, int i1, Field field) { - updateConditionState("cursor moved"); + updateConditionState(CURSOR_MOVED); } @Override diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250Client.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250Client.java index 5db5692d..70b9476f 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250Client.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250Client.java @@ -5,9 +5,9 @@ import com.blazemeter.jmeter.rte.core.CoordInput; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import com.blazemeter.jmeter.rte.core.Position; import com.blazemeter.jmeter.rte.core.Screen; -import com.blazemeter.jmeter.rte.core.TabulatorInput; import com.blazemeter.jmeter.rte.core.TerminalType; import com.blazemeter.jmeter.rte.core.exceptions.ConnectionClosedException; import com.blazemeter.jmeter.rte.core.exceptions.InvalidFieldLabelException; @@ -27,10 +27,10 @@ import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.ConnectionEndTerminalListener; import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.ScreenTextListener; import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.SilenceListener; -import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.Tn5250ConditionWaiter; import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.Tn5250TerminalStateListenerProxy; import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.UnlockListener; import com.blazemeter.jmeter.rte.protocols.tn5250.listeners.VisibleCursorListener; +import com.blazemeter.jmeter.rte.sampler.NavigationType; import java.awt.Dimension; import java.awt.event.KeyEvent; import java.util.Arrays; @@ -43,6 +43,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; +import javax.naming.OperationNotSupportedException; import net.infordata.em.TerminalClient; import net.infordata.em.crt5250.XI5250Field; import net.infordata.em.tn5250.XI5250EmulatorListener; @@ -104,7 +105,7 @@ public List getSupportedTerminalTypes() { @Override public void connect(String server, int port, SSLType sslType, TerminalType terminalType, long timeoutMillis) throws RteIOException, TimeoutException, InterruptedException { - stableTimeoutExecutor = Executors.newSingleThreadScheduledExecutor(); + stableTimeoutExecutor = Executors.newSingleThreadScheduledExecutor(NAMED_THREAD_FACTORY); /* we need create terminalClient instance on connect instead of constructor to avoid leaving keyboard thread running when @@ -145,27 +146,18 @@ public void onConnectionClosed() { } @Override - protected void setField(Input i) { + protected void setField(Input i, long echoTimeoutMillis) { if (i instanceof CoordInput) { setFieldByCoord((CoordInput) i); } else if (i instanceof LabelInput) { setFieldByLabel((LabelInput) i); - } else if (i instanceof TabulatorInput) { - setFieldByTabulator((TabulatorInput) i); + } else if (i instanceof NavigationInput) { + setFieldByNavigationType((NavigationInput) i); } else { throw new IllegalArgumentException("Invalid input type: " + i.getClass()); } } - private void setFieldByTabulator(TabulatorInput i) { - try { - client - .setFieldTextByTabulator(i.getOffset(), i.getInput()); - } catch (NoSuchElementException e) { - throw new ScreenWithoutFieldException(); - } - } - private void setFieldByCoord(CoordInput i) { try { client.setFieldTextByCoord(i.getPosition().getRow(), @@ -183,6 +175,34 @@ private void setFieldByLabel(LabelInput i) { } } + private void setFieldByNavigationType(NavigationInput i) { + if (i.getNavigationType().equals(NavigationType.TAB)) { + try { + client + .setFieldTextByTabulator(i.getRepeat(), i.getInput()); + } catch (NoSuchElementException e) { + throw new ScreenWithoutFieldException(); + } + } else { + if (Arrays.asList(NavigationType.values()).contains(i.getNavigationType())) { + Position currentPos = + getCursorPosition().orElseThrow(() -> new NoSuchElementException("No position " + + "available")); + + Position finalPosition = i + .calculateInputFinalPosition(currentPos, + new Dimension(client.getScreenDimensions())); + client + .setFieldTextByCoord(finalPosition.getRow(), finalPosition.getColumn(), + i.getInput()); + } else { + exceptionHandler + .setPendingError(new OperationNotSupportedException( + "The navigation type \'" + i.getNavigationType() + "\' is not supported")); + } + } + } + @Override protected void sendAttentionKey(AttentionKey attentionKey) { client.sendKeyEvent(getKeyEvent(attentionKey).specialKey, getKeyEvent(attentionKey).modifier); @@ -200,25 +220,23 @@ private KeyEventMap getKeyEvent(AttentionKey attentionKey) { } @Override - protected ConditionWaiter buildWaiter(WaitCondition waitCondition) { - Tn5250ConditionWaiter condition; + protected ConditionWaiter buildWaiter(WaitCondition waitCondition) { if (waitCondition instanceof SyncWaitCondition) { - condition = new UnlockListener((SyncWaitCondition) waitCondition, this, + return new UnlockListener((SyncWaitCondition) waitCondition, this, stableTimeoutExecutor, exceptionHandler); } else if (waitCondition instanceof CursorWaitCondition) { - condition = new VisibleCursorListener((CursorWaitCondition) waitCondition, + return new VisibleCursorListener((CursorWaitCondition) waitCondition, this, stableTimeoutExecutor, exceptionHandler); } else if (waitCondition instanceof SilentWaitCondition) { - condition = new SilenceListener((SilentWaitCondition) waitCondition, + return new SilenceListener((SilentWaitCondition) waitCondition, this, stableTimeoutExecutor, exceptionHandler); } else if (waitCondition instanceof TextWaitCondition) { - condition = new ScreenTextListener((TextWaitCondition) waitCondition, this, + return new ScreenTextListener((TextWaitCondition) waitCondition, this, stableTimeoutExecutor, exceptionHandler); } else { throw new UnsupportedOperationException( "We still don't support " + waitCondition.getClass().getName() + " waiters"); } - return condition; } @Override @@ -275,8 +293,8 @@ private int getFieldLinealPosition(XI5250Field field, Dimension screenSize) { } @Override - public boolean isInputInhibited() { - return client == null || client.isKeyboardLocked(); + public Optional isInputInhibited() { + return Optional.of(client == null || client.isKeyboardLocked()); } @Override diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/SilenceListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/SilenceListener.java index ded3ddba..66537234 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/SilenceListener.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/SilenceListener.java @@ -61,7 +61,8 @@ protected boolean getCurrentConditionState() { } private void handleReceivedEvent(XI5250EmulatorEvent event) { - /*we are updating over here because + /* + we are updating over here because silent does not really have a condition. Then always when some event arrives we need to startStablePeriod again. diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/Tn5250ConditionWaiter.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/Tn5250ConditionWaiter.java index d3845497..cee74f4a 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/Tn5250ConditionWaiter.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/Tn5250ConditionWaiter.java @@ -31,8 +31,7 @@ public Tn5250ConditionWaiter(T condition, Tn5250Client client, } protected static List getEventNames() { - Field[] declaredFields = XI5250EmulatorEvent.class.getDeclaredFields(); - return Arrays.stream(declaredFields) + return Arrays.stream(XI5250EmulatorEvent.class.getDeclaredFields()) .filter(f -> Modifier.isStatic(f.getModifiers()) && Modifier.isPublic(f.getModifiers())) .map(Field::getName) .collect(Collectors.toList()); diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListener.java index 781a55f0..c2b3c4ac 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListener.java +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListener.java @@ -23,6 +23,6 @@ public synchronized void stateChanged(XI5250EmulatorEvent event) { @Override protected boolean getCurrentConditionState() { - return !client.isInputInhibited(); + return !client.isInputInhibited().get(); } } diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/Vt420Client.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/Vt420Client.java new file mode 100644 index 00000000..b0bf19ab --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/Vt420Client.java @@ -0,0 +1,316 @@ +package com.blazemeter.jmeter.rte.protocols.vt420; + +import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.BaseProtocolClient; +import com.blazemeter.jmeter.rte.core.CharacterBasedProtocolClient; +import com.blazemeter.jmeter.rte.core.Input; +import com.blazemeter.jmeter.rte.core.NavigationInput; +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.Screen; +import com.blazemeter.jmeter.rte.core.TerminalType; +import com.blazemeter.jmeter.rte.core.exceptions.ConnectionClosedException; +import com.blazemeter.jmeter.rte.core.exceptions.RteIOException; +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.listener.TerminalStateListener; +import com.blazemeter.jmeter.rte.core.ssl.SSLType; +import com.blazemeter.jmeter.rte.core.wait.ConditionWaiter; +import com.blazemeter.jmeter.rte.core.wait.CursorWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.SilentWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.TextWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.WaitCondition; +import com.blazemeter.jmeter.rte.protocols.vt420.listeners.ScreenTextListener; +import com.blazemeter.jmeter.rte.protocols.vt420.listeners.SilenceListener; +import com.blazemeter.jmeter.rte.protocols.vt420.listeners.UnlockListener; +import com.blazemeter.jmeter.rte.protocols.vt420.listeners.VisibleCursorListener; +import com.blazemeter.jmeter.rte.protocols.vt420.listeners.Vt420TerminalStateListenerProxy; +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import com.blazemeter.jmeter.rte.sampler.RTESampler; +import java.awt.Dimension; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import nl.lxtreme.jvt220.terminal.ExceptionListener; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; +import nl.lxtreme.jvt220.terminal.TerminalClient; +import org.apache.commons.net.telnet.InvalidTelnetOptionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Vt420Client extends BaseProtocolClient implements CharacterBasedProtocolClient { + + public static final Map NAVIGATION_KEYS = buildNavigationKeysMapping(); + public static final Map ATTENTION_KEYS = buildAttKeysMapping(); + private static final Logger LOG = LoggerFactory.getLogger(Vt420Client.class); + private TerminalClient client; + private Map listeners = + new ConcurrentHashMap<>(); + + private static EnumMap buildNavigationKeysMapping() { + return new EnumMap(NavigationType.class) { + { + put(NavigationType.TAB, "\t"); + put(NavigationType.UP, "\033[A"); + put(NavigationType.DOWN, "\033[B"); + put(NavigationType.LEFT, "\033[D"); + put(NavigationType.RIGHT, "\033[C"); + } + }; + } + + private static EnumMap buildAttKeysMapping() { + return new EnumMap(AttentionKey.class) { + { + put(AttentionKey.ENTER, "\r"); + put(AttentionKey.F1, "\033[11~"); + put(AttentionKey.F2, "\033[12~"); + put(AttentionKey.F3, "\033[13~"); + put(AttentionKey.F4, "\033[14~"); + put(AttentionKey.F5, "\033[15~"); + put(AttentionKey.F6, "\033[17~"); + put(AttentionKey.F7, "\033[18~"); + put(AttentionKey.F8, "\033[19~"); + put(AttentionKey.F9, "\033[20~"); + put(AttentionKey.F10, "\033[21~"); + put(AttentionKey.F11, "\033[23~"); + put(AttentionKey.F12, "\033[24~"); + put(AttentionKey.F13, "\033[25~"); + put(AttentionKey.F14, "\033[26~"); + put(AttentionKey.F15, "\033[28~"); + put(AttentionKey.F16, "\033[29~"); + put(AttentionKey.F17, "\033[31~"); + put(AttentionKey.F18, "\033[32~"); + put(AttentionKey.F19, "\033[33~"); + put(AttentionKey.F20, "\033[34~"); + put(AttentionKey.F21, "\033[35~"); + put(AttentionKey.F22, "\033[36~"); + put(AttentionKey.F23, "\033[37~"); + put(AttentionKey.F24, "\033[38~"); + put(AttentionKey.ROLL_UP, "\033[5~"); + put(AttentionKey.ROLL_DN, "\033[6~"); + } + }; + } + + @Override + protected void setField(Input input, long echoTimeoutMillis) { + if (input instanceof NavigationInput) { + NavigationInput navigationInput = (NavigationInput) input; + NavigationType navigationType = navigationInput.getNavigationType(); + if (NAVIGATION_KEYS.get(navigationType) != null) { + processArrowKey(echoTimeoutMillis, navigationInput); + } else { + LOG.error("Navigation type {} not supported", navigationType); + throw new IllegalArgumentException("Navigation type not supported"); + } + } else { + LOG.error("{} not supported for VT420 protocol. Use Navigation inputs instead", + input.getClass().getSimpleName()); + throw new IllegalArgumentException("Not supported input: " + input.getClass()); + } + } + + private List textToList(String text) { + return text.chars().mapToObj(c -> ((char) c)).collect(Collectors.toList()).stream() + .map(String::valueOf) + .collect(Collectors.toList()); + } + + private void sendCharacterByOneAtATime(List text, long timeout) { + Semaphore semaphore = new Semaphore(0); + ScreenChangeListener listener = s -> semaphore.release(); + client.addScreenChangeListener(listener); + try { + for (String character : text) { + client.sendTextByCurrentCursorPosition(character); + if (!semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) { + exceptionHandler.setPendingError( + new TimeoutException( + "No server response after waiting '" + timeout + "' milliseconds when sending " + + "'" + character + "' character of '" + text + "'.")); + LOG.warn("If you consider that the character timeout is too low " + + "you can change the value by adding the line `RTEConnectionConfig" + + ".characterTimeoutMillis=` in the jmeter.properties file."); + } + } + } catch (IOException e) { + exceptionHandler.setPendingError(e); + } catch (InterruptedException ex) { + LOG.debug("Send of '{}' has been interrupted", text, ex); + exceptionHandler.setPendingError(ex); + Thread.currentThread().interrupt(); + } + client.removeScreenChangeListener(listener); + } + + private void processArrowKey(long echoTimeoutMillis, NavigationInput navigationInput) { + List input = new ArrayList<>(); + IntStream.range(0, navigationInput.getRepeat()) + .forEach(e -> input.add(NAVIGATION_KEYS.get(navigationInput.getNavigationType()))); + input.addAll(textToList(navigationInput.getInput())); + sendCharacterByOneAtATime(input, echoTimeoutMillis); + } + + protected void sendAttentionKey(AttentionKey attentionKey) { + String input = ATTENTION_KEYS.get(attentionKey); + if (input == null) { + throw new UnsupportedOperationException( + attentionKey.name() + " attentionKey is unsupported for protocol VT420."); + } + try { + client.sendTextByCurrentCursorPosition(input); + } catch (IOException e) { + exceptionHandler.setPendingError(e); + } + } + + @Override + protected ConditionWaiter buildWaiter(WaitCondition waitCondition) { + if (waitCondition instanceof SyncWaitCondition) { + return new UnlockListener((SyncWaitCondition) waitCondition, this, stableTimeoutExecutor, + exceptionHandler); + } else if (waitCondition instanceof TextWaitCondition) { + return new ScreenTextListener((TextWaitCondition) waitCondition, this, + stableTimeoutExecutor, + exceptionHandler); + } else if (waitCondition instanceof SilentWaitCondition) { + return new SilenceListener((SilentWaitCondition) waitCondition, this, stableTimeoutExecutor, + exceptionHandler); + } else if (waitCondition instanceof CursorWaitCondition) { + return new VisibleCursorListener((CursorWaitCondition) waitCondition, this, + stableTimeoutExecutor, + exceptionHandler); + } else { + throw new UnsupportedOperationException("Wait condition not supported yet."); + } + } + + @Override + protected void doDisconnect() { + try { + stableTimeoutExecutor.shutdownNow(); + stableTimeoutExecutor = null; + client.disconnect(); + } catch (IOException e) { + exceptionHandler.setPendingError(new RteIOException(e, "")); + } + } + + @Override + public List getSupportedTerminalTypes() { + return Collections.singletonList( + new TerminalType("VT420-7", new Dimension(80, 24))); + } + + @Override + public void connect(String server, int port, SSLType sslType, TerminalType terminalType, + long timeoutMillis) throws RteIOException { + client = new TerminalClient(terminalType.getScreenSize(), terminalType.getId()); + stableTimeoutExecutor = Executors.newSingleThreadScheduledExecutor(NAMED_THREAD_FACTORY); + exceptionHandler = new ExceptionHandler(server); + client.setSocketFactory(getSocketFactory(sslType, server)); + client.setExceptionListener(new ExceptionListener() { + @Override + public void onException(Throwable throwable) { + exceptionHandler.setPendingError(throwable); + } + + @Override + public void onConnectionClosed() { + exceptionHandler.setPendingError(new ConnectionClosedException()); + } + }); + + listeners.forEach((stateListener, listenerProxy) -> client + .addScreenChangeListener(listeners.get(stateListener))); + + try { + client.connect(server, port, (int) timeoutMillis); + } catch (IOException | InvalidTelnetOptionException e) { + LOG.error("Connection error: ", e); + throw new RteIOException(new Throwable("Connection error"), server); + } + + exceptionHandler.throwAnyPendingError(); + } + + @Override + public void addTerminalStateListener(TerminalStateListener terminalStateListener) { + Vt420TerminalStateListenerProxy listenerProxy = new Vt420TerminalStateListenerProxy( + terminalStateListener); + listeners.put(terminalStateListener, listenerProxy); + if (client != null) { + client.addScreenChangeListener(listenerProxy); + } + } + + @Override + public void removeTerminalStateListener(TerminalStateListener terminalStateListener) { + Vt420TerminalStateListenerProxy listenerProxy = listeners.remove(terminalStateListener); + if (client != null) { + client.removeScreenChangeListener(listenerProxy); + } + } + + @Override + public Screen getScreen() { + return Screen.buildScreenFromText(client.getScreen(), client.getScreenSize()); + } + + @Override + public Optional isInputInhibited() { + return Optional.empty(); + } + + @Override + public Optional getCursorPosition() { + return client.getCursorPosition().map(point -> new Position(point.y + 1, point.x + 1)); + } + + @Override + public boolean isAlarmOn() { + return false; + } + + @Override + public boolean resetAlarm() { + return false; + } + + @Override + public Set getSupportedAttentionKeys() { + return ATTENTION_KEYS.keySet(); + } + + @Override + public void addScreenChangeListener(ScreenChangeListener listener) { + client.addScreenChangeListener(listener); + } + + @Override + public void removeScreenChangeListener(ScreenChangeListener listener) { + client.removeScreenChangeListener(listener); + } + + @Override + public void send(String character) { + sendCharacterByOneAtATime(Collections.singletonList(character), + RTESampler.getCharacterTimeout()); + } + + public void setExceptionHandler(ExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/ScreenTextListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/ScreenTextListener.java new file mode 100644 index 00000000..67b8588b --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/ScreenTextListener.java @@ -0,0 +1,35 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.wait.TextWaitCondition; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import java.util.concurrent.ScheduledExecutorService; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; + +public class ScreenTextListener extends Vt420ConditionWaiter implements + ScreenChangeListener { + + public ScreenTextListener(TextWaitCondition condition, + Vt420Client client, + ScheduledExecutorService stableTimeoutExecutor, + ExceptionHandler exceptionHandler) { + super(condition, client, stableTimeoutExecutor, exceptionHandler); + client.addScreenChangeListener(this); + } + + @Override + public void screenChanged(String s) { + updateConditionState(SCREEN_CHANGED); + } + + @Override + public void stop() { + super.stop(); + client.removeScreenChangeListener(this); + } + + @Override + protected boolean getCurrentConditionState() { + return condition.matchesScreen(client.getScreen()); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/SilenceListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/SilenceListener.java new file mode 100644 index 00000000..33697b78 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/SilenceListener.java @@ -0,0 +1,42 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.wait.SilentWaitCondition; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import java.util.concurrent.ScheduledExecutorService; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; + +public class SilenceListener extends Vt420ConditionWaiter implements + ScreenChangeListener { + + public SilenceListener(SilentWaitCondition condition, + Vt420Client client, + ScheduledExecutorService stableTimeoutExecutor, + ExceptionHandler exceptionHandler) { + super(condition, client, stableTimeoutExecutor, exceptionHandler); + client.addScreenChangeListener(this); + } + + @Override + public void screenChanged(String s) { + /* + we are updating over here because + silent does not really have a + condition. Then always when some event + arrives we need to startStablePeriod again. + */ + lastConditionState = false; + updateConditionState(SCREEN_CHANGED); + } + + @Override + public void stop() { + super.stop(); + client.removeScreenChangeListener(this); + } + + @Override + protected boolean getCurrentConditionState() { + return true; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/UnlockListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/UnlockListener.java new file mode 100644 index 00000000..5c81e860 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/UnlockListener.java @@ -0,0 +1,42 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import java.util.concurrent.ScheduledExecutorService; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; + +public class UnlockListener extends Vt420ConditionWaiter implements + ScreenChangeListener { + + public UnlockListener(SyncWaitCondition condition, + Vt420Client client, + ScheduledExecutorService stableTimeoutExecutor, + ExceptionHandler exceptionHandler) { + super(condition, client, stableTimeoutExecutor, exceptionHandler); + client.addScreenChangeListener(this); + } + + @Override + public void stop() { + client.removeScreenChangeListener(this); + super.stop(); + } + + @Override + protected boolean getCurrentConditionState() { + return true; + } + + @Override + public void screenChanged(String s) { + /* + not a good practice to update a variable from here + but considering that sync has the same behaviour as + silent wait. It is the best approach here. + */ + lastConditionState = false; + updateConditionState(SCREEN_CHANGED); + } + +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/VisibleCursorListener.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/VisibleCursorListener.java new file mode 100644 index 00000000..b3362b61 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/VisibleCursorListener.java @@ -0,0 +1,35 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.wait.CursorWaitCondition; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import java.util.concurrent.ScheduledExecutorService; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; + +public class VisibleCursorListener extends Vt420ConditionWaiter implements + ScreenChangeListener { + + public VisibleCursorListener(CursorWaitCondition condition, + Vt420Client client, + ScheduledExecutorService stableTimeoutExecutor, + ExceptionHandler exceptionHandler) { + super(condition, client, stableTimeoutExecutor, exceptionHandler); + client.addScreenChangeListener(this); + } + + @Override + public void screenChanged(String s) { + updateConditionState(SCREEN_CHANGED); + } + + @Override + public void stop() { + super.stop(); + client.removeScreenChangeListener(this); + } + + @Override + protected boolean getCurrentConditionState() { + return condition.getPosition().equals(client.getCursorPosition().orElse(null)); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420ConditionWaiter.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420ConditionWaiter.java new file mode 100644 index 00000000..a0e01c7d --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420ConditionWaiter.java @@ -0,0 +1,21 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.wait.ConditionWaiter; +import com.blazemeter.jmeter.rte.core.wait.WaitCondition; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import java.util.concurrent.ScheduledExecutorService; + +public abstract class Vt420ConditionWaiter extends ConditionWaiter { + + protected static final String SCREEN_CHANGED = "screenChanged"; + protected final Vt420Client client; + + public Vt420ConditionWaiter(T condition, + Vt420Client client, ScheduledExecutorService stableTimeoutExecutor, + ExceptionHandler exceptionHandler) { + super(condition, stableTimeoutExecutor, exceptionHandler); + this.client = client; + initialVerificationOfCondition(); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420TerminalStateListenerProxy.java b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420TerminalStateListenerProxy.java new file mode 100644 index 00000000..f4903a71 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420TerminalStateListenerProxy.java @@ -0,0 +1,18 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listener.TerminalStateListener; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; + +public class Vt420TerminalStateListenerProxy implements ScreenChangeListener { + + private TerminalStateListener listener; + + public Vt420TerminalStateListenerProxy(TerminalStateListener listener) { + this.listener = listener; + } + + @Override + public void screenChanged(String s) { + listener.onTerminalStateChange(); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/RTERecorder.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/RTERecorder.java index 37479a4b..daf2d00a 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/recorder/RTERecorder.java +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/RTERecorder.java @@ -1,6 +1,7 @@ package com.blazemeter.jmeter.rte.recorder; import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.CharacterBasedProtocolClient; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.Protocol; import com.blazemeter.jmeter.rte.core.RteProtocolClient; @@ -10,6 +11,8 @@ import com.blazemeter.jmeter.rte.core.listener.RequestListener; import com.blazemeter.jmeter.rte.core.listener.TerminalStateListener; import com.blazemeter.jmeter.rte.core.ssl.SSLType; +import com.blazemeter.jmeter.rte.recorder.emulator.CharacterBasedEmulator; +import com.blazemeter.jmeter.rte.recorder.emulator.FieldBasedEmulator; import com.blazemeter.jmeter.rte.recorder.emulator.TerminalEmulator; import com.blazemeter.jmeter.rte.recorder.emulator.TerminalEmulatorListener; import com.blazemeter.jmeter.rte.recorder.emulator.Xtn5250TerminalEmulator; @@ -20,6 +23,7 @@ import com.blazemeter.jmeter.rte.sampler.gui.RTESamplerGui; import com.helger.commons.annotation.VisibleForTesting; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.concurrent.ExecutorService; @@ -66,28 +70,29 @@ public class RTERecorder extends GenericController implements TerminalEmulatorLi private transient int sampleCount; private transient WaitConditionsRecorder waitConditionsRecorder; private transient ExecutorService connectionExecutor; - + private transient Function, List> inputProvider; private transient Function protocolFactory; private transient RteProtocolClient terminalClient; private transient List responseAssertions = new ArrayList<>(); public RTERecorder() { - this(Xtn5250TerminalEmulator::new, new RecordingTargetFinder(), + this(new RecordingTargetFinder(), Protocol::createProtocolClient); } - public RTERecorder(Supplier supplier, RecordingTargetFinder finder, + public RTERecorder(RecordingTargetFinder finder, Function factory) { - terminalEmulatorSupplier = supplier; this.finder = finder; this.protocolFactory = factory; + this.terminalEmulatorSupplier = () -> null; } @VisibleForTesting public RTERecorder(Supplier supplier, RecordingTargetFinder finder, Function factory, JMeterTreeModel treeModelMock) { - this(supplier, finder, factory); + this(finder, factory); + this.terminalEmulatorSupplier = supplier; this.treeModelMock = treeModelMock; } @@ -189,6 +194,11 @@ public void setTimeoutThresholdMillis(String waitConditionsTimeoutThresholdMilli setProperty(WAIT_CONDITION_TIMEOUT_THRESHOLD_MILLIS_PROPERTY, timeoutThresholdMillis); } + @VisibleForTesting + public void setInputProvider(Function, List> inputProvider) { + this.inputProvider = inputProvider; + } + @Override public void onRecordingStart() { LOG.debug("Start recording"); @@ -205,18 +215,20 @@ public void onRecordingStart() { getTimeoutThresholdMillis(), RTESampler.getStableTimeout()); waitConditionsRecorder.start(); ExecutorService executor = Executors.newSingleThreadExecutor(); - // we use a separate variable to avoid shutting down an incorrect executor if there are two - // threads connecting (on start, stop and start) + /* + we use a separate variable to avoid shutting down an incorrect executor if there are two + threads connecting (on start, stop and start) + */ this.connectionExecutor = executor; executor.submit(() -> { try { synchronized (this) { - terminalClient .connect(getServer(), getPort(), getSSLType(), terminalType, getConnectionTimeout()); resultBuilder.withConnectEndNow(); + initTerminalEmulatorSupplier(); initTerminalEmulator(terminalType); - registerRequestListenerFor(resultBuilder); + registerRequestListenerFor(); } } catch (Exception e) { onException(e); @@ -226,6 +238,22 @@ public void onRecordingStart() { }); } + private void initTerminalEmulatorSupplier() { + //verification done in order to not override the mock when testing + if (terminalEmulatorSupplier.get() == null) { + inputProvider = inputs -> terminalClient instanceof CharacterBasedProtocolClient ? Collections + .emptyList() : inputs; + if (terminalClient instanceof CharacterBasedProtocolClient) { + CharacterBasedEmulator characterBasedEmulator = new CharacterBasedEmulator(); + terminalEmulatorSupplier = () -> new Xtn5250TerminalEmulator(characterBasedEmulator); + ((CharacterBasedProtocolClient) terminalClient) + .addScreenChangeListener(characterBasedEmulator); + } else { + terminalEmulatorSupplier = () -> new Xtn5250TerminalEmulator(new FieldBasedEmulator()); + } + } + } + @VisibleForTesting protected void awaitConnected(long timeout) throws InterruptedException, TimeoutException { connectionExecutor.shutdown(); @@ -297,7 +325,7 @@ private RteSampleResultBuilder buildSampleResultBuilder(Action action) { .withSslType(getSSLType()) .withAction(action); if (action != Action.CONNECT) { - ret.withInputInhibitedRequest(terminalClient.isInputInhibited()); + ret.withInputInhibitedRequest(terminalClient.isInputInhibited().orElse(false)); } return ret; } @@ -332,12 +360,14 @@ private void initTerminalEmulator(TerminalType terminalType) { terminalEmulator .setScreenSize(terminalType.getScreenSize().width, terminalType.getScreenSize().height); terminalEmulator.setSupportedAttentionKeys(terminalClient.getSupportedAttentionKeys()); + terminalEmulator.setProtocolClient(terminalClient); terminalEmulator.start(); terminalClient.addTerminalStateListener(this); + onTerminalStateChange(); } - private void registerRequestListenerFor(RteSampleResultBuilder resultBuilder) { + private void registerRequestListenerFor() { requestListener = new RequestListener<>(resultBuilder, terminalClient); terminalClient.addTerminalStateListener(requestListener); } @@ -346,16 +376,16 @@ private void registerRequestListenerFor(RteSampleResultBuilder resultBuilder) { public void onAttentionKey(AttentionKey attentionKey, List inputs, String screenName) { samplerName = screenName; sampleCount++; - terminalEmulator.setKeyboardLock(true); requestListener.stop(); recordPendingSample(); terminalClient.resetAlarm(); resultBuilder = buildSendInputSampleResultBuilder(attentionKey, inputs); - registerRequestListenerFor(resultBuilder); + registerRequestListenerFor(); sampler = buildSampler(Action.SEND_INPUT, inputs, attentionKey); try { waitConditionsRecorder.start(); - terminalClient.send(inputs, attentionKey); + terminalClient.send(inputProvider.apply(inputs), attentionKey, + RTESampler.DEFAULT_CONNECTION_TIMEOUT_MILLIS); } catch (Exception e) { onException(e); } @@ -412,6 +442,7 @@ public void onRecordingStop() { sampler = buildSampler(Action.DISCONNECT, null, null); terminalEmulator.stop(); terminalEmulator = null; + terminalEmulatorSupplier = () -> null; requestListener.stop(); try { terminalClient.disconnect(); @@ -451,7 +482,9 @@ public void onTerminalStateChange() { terminalEmulator.setScreen(terminalClient.getScreen(), samplerName); terminalClient.getCursorPosition().ifPresent(cursorPosition -> terminalEmulator .setCursor(cursorPosition.getRow(), cursorPosition.getColumn())); - terminalEmulator.setKeyboardLock(terminalClient.isInputInhibited()); + if (terminalClient.isInputInhibited().isPresent()) { + terminalEmulator.setKeyboardLock(terminalClient.isInputInhibited().get()); + } if (terminalClient.isAlarmOn()) { terminalEmulator.soundAlarm(); } @@ -468,7 +501,7 @@ public void onException(Throwable e) { if (recordingListener != null) { recordingListener.onRecordingException((Exception) e); } - + resultBuilder.withFailure(e); recordPendingSample(); synchronized (this) { @@ -477,6 +510,7 @@ public void onException(Throwable e) { terminalClient.removeTerminalStateListener(this); terminalEmulator.stop(); terminalEmulator = null; + terminalEmulatorSupplier = () -> null; } } diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/CharacterBasedEmulator.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/CharacterBasedEmulator.java new file mode 100644 index 00000000..b58cad1b --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/CharacterBasedEmulator.java @@ -0,0 +1,298 @@ +package com.blazemeter.jmeter.rte.recorder.emulator; + +import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.CharacterBasedProtocolClient; +import com.blazemeter.jmeter.rte.core.Input; +import com.blazemeter.jmeter.rte.core.NavigationInput.NavigationInputBuilder; +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.Screen; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import java.awt.Dimension; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CharacterBasedEmulator extends + XI5250CrtBase implements ScreenChangeListener { + + private static final Logger LOG = LoggerFactory.getLogger(CharacterBasedEmulator.class); + private Position lastCursorPosition; + private StringBuilder inputBuffer = new StringBuilder(); + private List inputs = new ArrayList<>(); + private int repetition; + private Screen lastTerminalScreen; + private boolean isAreaSelected; + private Screen currentScreen; + private SwingWorker currentSwingWorker; + private int pastingCharactersCount = 0; + private int pastedCharactersCount = 0; + private NavigationInputBuilder currentInput = new NavigationInputBuilder(); + + @Override + protected synchronized void processKeyEvent(KeyEvent e) { + LOG.debug("Processing key: ({})", e); + processCopyOrPaste(e); + if (isAnyKeyPressedOrControlKeyReleasedAndNotCopy(e)) { + AttentionKey attentionKey = KEY_EVENTS + .get(new KeyEventMap(e.getModifiers(), e.getKeyCode())); + if (attentionKey == null && !locked) { + if (e.getKeyCode() == KeyEvent.VK_SHIFT + || e.getKeyCode() == KeyEvent.VK_META + || e.getKeyCode() == KeyEvent.KEY_LOCATION_UNKNOWN + || copyPaste) { + copyPaste = false; + return; + } + lockEmulator(true); + currentSwingWorker = buildSwingWorker(e); + currentSwingWorker.execute(); + } else if (attentionKey != null) { + if (isAttentionKeyValid(attentionKey)) { + lockEmulator(true); + lastCursorPosition = getCursorPosition(); + processAttentionKey(e, attentionKey); + lastTerminalScreen = new Screen(currentScreen); + } else { + showUserMessage(attentionKey + " not supported for current protocol"); + e.consume(); + } + } + } + } + + @Override + protected List getInputFields() { + if (inputBuffer.length() > 0 || repetition != 0) { + buildDefaultInputWhenNoNavigationType(); + insertCurrentInput(); + } + List inputs = new ArrayList<>(this.inputs); + this.inputs.clear(); + return inputs; + } + + private SwingWorker buildSwingWorker(KeyEvent e) { + return new SwingWorker() { + @Override + protected Object doInBackground() { + recordInput(getKeyString(e)); + return null; + } + }; + } + + @Override + public void setKeyboardLock(boolean lock) { + locked = lock; + statusPanel.setKeyboardStatus(lock); + } + + @Override + public synchronized void makePaste() { + String value; + try { + value = getClipboardContent(); + } catch (IOException | UnsupportedFlavorException e) { + LOG.warn("Error while trying to get clipboard content", e); + return; + } + List sequencesInClipboard = CharacterSequenceScaper.getSequencesIn(value); + + if (!sequencesInClipboard.isEmpty()) { + String chunkAppearances = CharacterSequenceScaper.getSequenceChunkAppearancesIn(value); + JOptionPane.showMessageDialog(this, "Clipboard content \'" + String.join(", ", + sequencesInClipboard) + "\' is not " + + "supported when pasting. \nAppearances of sequences near to: " + + chunkAppearances, "Paste error", + JOptionPane.INFORMATION_MESSAGE); + LOG.error("Clipboard content contains unsupported ANSI sequence. RTE-Plugin may support " + + "that/those sequence/s ({}) as an attention key or as a navigation input. " + + "\nAppearances of sequences: {} ", sequencesInClipboard, + chunkAppearances); + LOG.info( + "Check this page to understand what those characters means: https://en.wikipedia" + + ".org/wiki/List_of_Unicode_characters#Control_codes"); + return; + } + pastingCharactersCount = value.length(); + pastedCharactersCount = 0; + lockEmulator(true); + currentSwingWorker = new SwingWorker() { + @Override + protected Object doInBackground() { + Arrays.stream(value.split("")) + .forEach(c -> recordInput(c)); + return null; + } + }; + currentSwingWorker.execute(); + } + + private void lockEmulator(boolean isLock) { + setKeyboardLock(isLock); + setCursorVisible(!isLock); + pasteConsumer.accept(!isLock); + } + + private String getClipboardContent() throws IOException, UnsupportedFlavorException { + Clipboard clipboard = this.getToolkit().getSystemClipboard(); + return (String) clipboard.getContents(this).getTransferData(DataFlavor.stringFlavor); + } + + private void recordInput(String value) { + Position cursorPosition = getCursorPosition(); + terminalClient.send(value); + if (validInput(cursorPosition)) { + Optional navigationKey = Arrays.stream(NavigationType.values()) + .filter(v -> Vt420Client.NAVIGATION_KEYS.get(v).equals(value)) + .findFirst(); + if (navigationKey.isPresent()) { + buildNavigationInput(navigationKey.get()); + } else { + if (lastCursorPosition == null + || lastCursorPosition.isConsecutiveWith(cursorPosition) + || inputBuffer.length() == 0) { + inputBuffer.append(value); + } else { + buildDefaultInputWhenNoNavigationType(); + insertCurrentInput(); + currentInput.withNavigationType(NavigationType.TAB); + inputBuffer.append(value); + } + } + } + lastCursorPosition = new Position(cursorPosition); + lastTerminalScreen = new Screen(currentScreen); + } + + private void buildNavigationInput(NavigationType type) { + if (inputBuffer.length() > 0 || (repetition != 0 && !type + .equals(currentInput.getNavigationType()))) { + buildDefaultInputWhenNoNavigationType(); + insertCurrentInput(); + } + if (currentInput.getNavigationType() == null) { + currentInput.withNavigationType(type); + } + currentInput.withRepeat(++repetition); + } + + private void buildDefaultInputWhenNoNavigationType() { + if (currentInput.getNavigationType() == null) { + currentInput = new NavigationInputBuilder() + .withRepeat(repetition) + .withNavigationType(NavigationType.TAB); + } + } + + private void insertCurrentInput() { + inputs.add(currentInput.withInput(inputBuffer.toString()).build()); + currentInput = new NavigationInputBuilder(); + inputBuffer = new StringBuilder(); + repetition = 0; + } + + private boolean validInput(Position positionBeforeSend) { + if (currentScreen.equals(lastTerminalScreen) && getCursorPosition() + .equals(lastCursorPosition)) { + if (!positionBeforeSend.equals(lastCursorPosition)) { + //in order to notice a difference when moving backwards like: LEFT + lastCursorPosition = positionBeforeSend; + return true; + } + return false; + } + return true; + } + + private Position getCursorPosition() { + return terminalClient.getCursorPosition().orElse(Position.DEFAULT_POSITION); + } + + private String getKeyString(KeyEvent e) { + int keyCode = e.getKeyCode(); + switch (keyCode) { + case KeyEvent.VK_TAB: + return Vt420Client.NAVIGATION_KEYS.get(NavigationType.TAB); + case KeyEvent.VK_LEFT: + return Vt420Client.NAVIGATION_KEYS.get(NavigationType.LEFT); + case KeyEvent.VK_RIGHT: + return Vt420Client.NAVIGATION_KEYS.get(NavigationType.RIGHT); + case KeyEvent.VK_UP: + return Vt420Client.NAVIGATION_KEYS.get(NavigationType.UP); + case KeyEvent.VK_DOWN: + return Vt420Client.NAVIGATION_KEYS.get(NavigationType.DOWN); + case KeyEvent.VK_SPACE: + return " "; + default: + return (KeyEvent.getKeyModifiersText(e.getModifiers()).isEmpty()) + ? KeyEvent.getKeyText(keyCode).toLowerCase() + : KeyEvent.getKeyText(keyCode); + } + } + + @Override + public synchronized void screenChanged(String s) { + currentScreen = Screen.buildScreenFromText(s, new Dimension(80, 24)); + if (pastingCharactersCount == 0) { + lockEmulator(false); + } else if (++pastedCharactersCount == pastingCharactersCount) { + lockEmulator(false); + pastingCharactersCount = 0; + } + } + + public void setKeyboardStatus(boolean isLock) { + locked = isLock; + statusPanel.setKeyboardStatus(isLock); + } + + @Override + public synchronized void teardown() { + lastTerminalScreen = null; + lastCursorPosition = null; + currentScreen = null; + terminalClient.removeScreenChangeListener(this); + if (currentSwingWorker != null) { + currentSwingWorker.cancel(true); + } + } + + @Override + protected void processMouseEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_CLICKED) { + if (isAreaSelected) { + super.setSelectedArea(null); + isAreaSelected = false; + } else { + statusPanel.blinkBlockedCursor(); + } + } else if (e.getID() == MouseEvent.MOUSE_PRESSED) { + super.setIvMousePressed(true); + super.setIvStartDragging(e); + } else if (e.getID() == MouseEvent.MOUSE_RELEASED) { + super.setIvMousePressed(false); + } + } + + @Override + protected void processMouseMotionEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + isAreaSelected = true; + } + super.processMouseMotionEvent(e); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/CharacterSequenceScaper.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/CharacterSequenceScaper.java new file mode 100644 index 00000000..a083dc13 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/CharacterSequenceScaper.java @@ -0,0 +1,73 @@ +package com.blazemeter.jmeter.rte.recorder.emulator; + +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.CharUtils; + +public class CharacterSequenceScaper { + + private static final int CHUNK_TEXT_SIZE = 5; + private static final Map SEQUENCES = buildCharacterSequences(); + + private static Map buildCharacterSequences() { + ImmutableMapBuilder mapBuilder = new ImmutableMapBuilder() + .put("\b") + .put("\n") + .put("\r") + .put("\f") + .put("\'") + .put("\"") + .put("\\"); + Vt420Client.NAVIGATION_KEYS.forEach((k, v) -> mapBuilder.put(v)); + Vt420Client.ATTENTION_KEYS.forEach((k, v) -> mapBuilder.put(v)); + return mapBuilder.build(); + } + + public static String getSequenceChunkAppearancesIn(String value) { + if (value == null) { + return null; + } + StringBuilder sequenceLocation = new StringBuilder(); + for (String sequence : SEQUENCES.keySet()) { + if (value.contains(sequence)) { + int index = value.indexOf(sequence); + int chunkBegin = index - CHUNK_TEXT_SIZE >= 0 ? index - CHUNK_TEXT_SIZE : 0; + int chunkEnd = index + CHUNK_TEXT_SIZE <= value.length() ? index + CHUNK_TEXT_SIZE + : value.length(); + String chunk = value.substring(chunkBegin, chunkEnd); + sequenceLocation.append(chunkBegin == 0 ? "\n" : "\n..."); + sequenceLocation.append(chunk); + sequenceLocation.append(chunkEnd == value.length() ? "" : "..."); + } + } + return sequenceLocation.toString(); + } + + public static List getSequencesIn(String text) { + List sequencesAppeared = new ArrayList<>(); + SEQUENCES.forEach((k, v) -> { + if (text.contains(k)) { + sequencesAppeared.add(v); + } + }); + return sequencesAppeared; + } + + public static class ImmutableMapBuilder { + + private Map map = new HashMap<>(); + + public ImmutableMapBuilder put(String key) { + map.put(key, CharUtils.unicodeEscaped(key.charAt(0))); + return this; + } + + public Map build() { + return map; + } + + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/FieldBasedEmulator.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/FieldBasedEmulator.java new file mode 100644 index 00000000..2aabb145 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/FieldBasedEmulator.java @@ -0,0 +1,96 @@ +package com.blazemeter.jmeter.rte.recorder.emulator; + +import com.blazemeter.jmeter.rte.core.Input; +import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import net.infordata.em.crt5250.XI5250Field; + +public class FieldBasedEmulator extends XI5250CrtBase { + + private int initialColumn; + private int initialRow; + + @Override + public void setKeyboardLock(boolean lock) { + locked = lock; + statusPanel.setKeyboardStatus(lock); + } + + @Override + public void makePaste() { + super.doPaste(); + } + + @Override + public void teardown() { + labelMap.clear(); + } + + @Override + protected List getInputFields() { + List fields = new ArrayList<>(); + XI5250Field fieldFromPos = getFieldFromPos(initialColumn, initialRow); + if (fieldFromPos == null) { + fieldFromPos = getNextFieldFromPos(initialColumn, initialRow); + } + int initialField = getFields().indexOf(fieldFromPos); + Iterator it = getFields().listIterator(initialField); + int index = 0; + int offset = 1; + while (it.hasNext() && index < getFields().size()) { + XI5250Field f = it.next(); + if (f.isMDTOn()) { + Position fieldPosition = new Position(f.getRow() + 1, f.getCol() + 1); + String label = (String) labelMap.get(fieldPosition); + String trimmedInput = trimNulls(f.getString()); + fields.add(label != null ? new LabelInput(label, trimmedInput) + : new NavigationInput(f.equals(fieldFromPos) ? 0 : offset, NavigationType.TAB, + trimmedInput)); + offset = 1; + } else { + offset++; + } + index++; + if (!it.hasNext()) { + it = getFields().iterator(); + } + } + labelMap.clear(); + return fields; + } + + @Override + public void setInitialCursorPos(int column, int row) { + this.initialColumn = column; + this.initialRow = row; + this.setCursorPos(column, row); + } + + private String trimNulls(String str) { + if (str.isEmpty()) { + return str; + } + int firstNotNull = 0; + while (str.charAt(firstNotNull) == '\u0000') { + firstNotNull++; + } + int lastNotNull = str.length() - 1; + while (str.charAt(lastNotNull) == '\u0000') { + lastNotNull--; + } + return str.substring(firstNotNull, lastNotNull + 1); + } + + @Override + protected void processMouseEvent(MouseEvent e) { + super.processMouseEvent(e); + statusPanel + .updateStatusBarCursorPosition(this.getCursorRow() + 1, this.getCursorCol() + 1); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/IntermittentLabel.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/IntermittentLabel.java new file mode 100644 index 00000000..7da72e94 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/IntermittentLabel.java @@ -0,0 +1,66 @@ +package com.blazemeter.jmeter.rte.recorder.emulator; + +import com.blazemeter.jmeter.rte.sampler.gui.ThemedIconLabel; +import com.helger.commons.annotation.VisibleForTesting; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class IntermittentLabel extends ThemedIconLabel { + + private static final int LABEL_ITERATION = 10; + private static final int BLINK_TIME_PERIOD_MILLIS = 500; + private final ScheduledExecutorService executor; + private ScheduledFuture future; + private int counter; + private boolean labelState = true; + private Runnable defaultTask; + private Runnable onBlinkTask; + + public IntermittentLabel(String iconResourceName) { + this(iconResourceName, Executors.newSingleThreadScheduledExecutor()); + } + + @VisibleForTesting + public IntermittentLabel(String iconResourceName, ScheduledExecutorService executor) { + super(iconResourceName); + setVisible(false); + this.executor = executor; + } + + public synchronized void blink() { + if (future != null) { + future.cancel(true); + defaultTask.run(); + } + counter = 0; + future = executor.scheduleAtFixedRate(() -> { + onBlinkTask.run(); + if (counter < LABEL_ITERATION) { + labelState = !labelState; + counter++; + } else { + future.cancel(true); + defaultTask.run(); + } + + }, 0, BLINK_TIME_PERIOD_MILLIS, TimeUnit.MILLISECONDS); + } + + public void setOnBlinkTask(Runnable onBlinkTask) { + this.onBlinkTask = onBlinkTask; + } + + public void setDefaultTask(Runnable defaultTask) { + this.defaultTask = defaultTask; + } + + public void shutdown() { + executor.shutdown(); + } + + public boolean getLabelState() { + return labelState; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanel.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanel.java index 71550b0a..10ba0479 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanel.java +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanel.java @@ -4,8 +4,6 @@ import com.blazemeter.jmeter.rte.sampler.gui.ThemedIconLabel; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import javax.swing.GroupLayout; import javax.swing.JLabel; import javax.swing.JPanel; @@ -16,25 +14,30 @@ public class StatusPanel extends JPanel { private static final String KEYBOARD_UNLOCKED_RESOURCE_NAME = "keyboard-unlocked.png"; private static final String VISIBLE_CREDENTIALS_ICON = "visible-credentials.png"; private static final String NOT_VISIBLE_CREDENTIAL_ICON = "not-visible-credentials.png"; - - private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + private static final String UNLOCKED_CURSOR_RESOURCE_NAME = "cursor.png"; + private static final String LOCKED_CURSOR_RESOURCE_NAME = "blocked-cursor.png"; + private static final String ALARM_RESOURCE_NAME = "alarm.png"; + private JLabel positionLabel = SwingUtils .createComponent("positionLabel", new JLabel("row: 00 / column: 00")); - private AlarmLabel alarmLabel = SwingUtils - .createComponent("alarmLabel", new AlarmLabel(executorService)); + + private IntermittentLabel alarmLabel = SwingUtils.createComponent("alarmLabel", + new IntermittentLabel(ALARM_RESOURCE_NAME)); + private ThemedIconLabel keyboardLabel = SwingUtils .createComponent("keyboardLabel", new ThemedIconLabel(KEYBOARD_LOCKED_RESOURCE_NAME)); private ThemedIconLabel showCredentials = SwingUtils .createComponent("showCredentials", new ThemedIconLabel(NOT_VISIBLE_CREDENTIAL_ICON)); - + private IntermittentLabel blockedCursor = SwingUtils.createComponent("blockedCursor", + new IntermittentLabel(LOCKED_CURSOR_RESOURCE_NAME)); private HelpFrame helpFrame; public StatusPanel() { JLabel helpLabel = SwingUtils .createComponent("helpLabel", new ThemedIconLabel("help.png")); helpLabel.addMouseListener(buildShowHelpOnMouseClickListener()); - + initIntermittentLabels(); GroupLayout layout = new GroupLayout(this); layout.setAutoCreateContainerGaps(true); layout.setAutoCreateGaps(true); @@ -44,15 +47,17 @@ public StatusPanel() { keyboardLabel.setToolTipText("Keyboard status"); helpLabel.setToolTipText("Help"); positionLabel.setToolTipText("Cursor Position"); - + blockedCursor.setToolTipText("Cursor movements using\nmouse are disable"); layout.setHorizontalGroup(layout.createSequentialGroup() .addComponent(positionLabel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) + .addComponent(blockedCursor) .addComponent(alarmLabel) .addComponent(showCredentials) .addComponent(keyboardLabel) .addComponent(helpLabel)); layout.setVerticalGroup(layout.createParallelGroup() + .addComponent(blockedCursor) .addComponent(positionLabel) .addComponent(alarmLabel) .addComponent(showCredentials) @@ -60,6 +65,16 @@ public StatusPanel() { .addComponent(helpLabel)); } + private void initIntermittentLabels() { + blockedCursor.setOnBlinkTask(() -> blockedCursor.setIconResourceName( + blockedCursor.getLabelState() ? UNLOCKED_CURSOR_RESOURCE_NAME + : LOCKED_CURSOR_RESOURCE_NAME)); + blockedCursor + .setDefaultTask(() -> blockedCursor.setIconResourceName(LOCKED_CURSOR_RESOURCE_NAME)); + alarmLabel.setDefaultTask(() -> alarmLabel.setVisible(false)); + alarmLabel.setOnBlinkTask(() -> alarmLabel.setVisible(!alarmLabel.isVisible())); + } + private MouseListener buildShowHelpOnMouseClickListener() { return new MouseListener() { @Override @@ -101,7 +116,7 @@ public void updateShowCredentials(boolean visible) { } public void soundAlarm() { - alarmLabel.soundAlarm(); + alarmLabel.blink(); } public JLabel getShowCredentials() { @@ -113,8 +128,17 @@ public void setKeyboardStatus(boolean locked) { locked ? KEYBOARD_LOCKED_RESOURCE_NAME : KEYBOARD_UNLOCKED_RESOURCE_NAME); } + public void setBlockedCursorVisible() { + blockedCursor.setVisible(true); + } + + public void blinkBlockedCursor() { + blockedCursor.blink(); + } + public void dispose() { - executorService.shutdown(); + blockedCursor.shutdown(); + alarmLabel.shutdown(); if (helpFrame != null) { helpFrame.close(); } diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/TerminalEmulator.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/TerminalEmulator.java index 68f9d783..2ac68f01 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/TerminalEmulator.java +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/TerminalEmulator.java @@ -1,6 +1,7 @@ package com.blazemeter.jmeter.rte.recorder.emulator; import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.RteProtocolClient; import com.blazemeter.jmeter.rte.core.Screen; import java.util.Set; @@ -23,4 +24,6 @@ public interface TerminalEmulator { void addTerminalEmulatorListener(TerminalEmulatorListener terminalEmulatorListener); void setSupportedAttentionKeys(Set supportedAttentionKeys); + + void setProtocolClient(RteProtocolClient terminalClient); } diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/XI5250CrtBase.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/XI5250CrtBase.java new file mode 100644 index 00000000..1c54e7b4 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/XI5250CrtBase.java @@ -0,0 +1,292 @@ +package com.blazemeter.jmeter.rte.recorder.emulator; + +import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.Input; +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.RteProtocolClient; +import com.blazemeter.jmeter.rte.core.Screen; +import com.blazemeter.jmeter.rte.recorder.emulator.Xtn5250TerminalEmulator.ScreenField; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import javax.swing.JOptionPane; +import net.infordata.em.crt5250.XI5250Crt; +import net.infordata.em.crt5250.XI5250Field; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class XI5250CrtBase extends XI5250Crt { + + public static final int DEFAULT_ATTR = 32; + protected static final Map KEY_EVENTS = + new HashMap() { + { + put(new KeyEventMap(0, KeyEvent.VK_F1), AttentionKey.F1); + put(new KeyEventMap(0, KeyEvent.VK_F2), AttentionKey.F2); + put(new KeyEventMap(0, KeyEvent.VK_F3), AttentionKey.F3); + put(new KeyEventMap(0, KeyEvent.VK_F4), AttentionKey.F4); + put(new KeyEventMap(0, KeyEvent.VK_F5), AttentionKey.F5); + put(new KeyEventMap(0, KeyEvent.VK_F6), AttentionKey.F6); + put(new KeyEventMap(0, KeyEvent.VK_F7), AttentionKey.F7); + put(new KeyEventMap(0, KeyEvent.VK_F8), AttentionKey.F8); + put(new KeyEventMap(0, KeyEvent.VK_F9), AttentionKey.F9); + put(new KeyEventMap(0, KeyEvent.VK_F10), AttentionKey.F10); + put(new KeyEventMap(0, KeyEvent.VK_F11), AttentionKey.F11); + put(new KeyEventMap(0, KeyEvent.VK_F12), AttentionKey.F12); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F1), AttentionKey.F13); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F2), AttentionKey.F14); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F3), AttentionKey.F15); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F4), AttentionKey.F16); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F5), AttentionKey.F17); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F6), AttentionKey.F18); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F7), AttentionKey.F19); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F8), AttentionKey.F20); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F9), AttentionKey.F21); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F10), AttentionKey.F22); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F11), AttentionKey.F23); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F12), AttentionKey.F24); + put(new KeyEventMap(0, KeyEvent.VK_ENTER), AttentionKey.ENTER); + put(new KeyEventMap(0, KeyEvent.VK_ESCAPE), AttentionKey.ATTN); + put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F4), AttentionKey.CLEAR); + put(new KeyEventMap(0, KeyEvent.VK_PAUSE), AttentionKey.CLEAR); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_ESCAPE), AttentionKey.SYSRQ); + put(new KeyEventMap(0, KeyEvent.VK_CONTROL), AttentionKey.RESET); + put(new KeyEventMap(KeyEvent.CTRL_MASK, KeyEvent.VK_CONTROL), AttentionKey.RESET); + put(new KeyEventMap(0, KeyEvent.VK_PAGE_DOWN), AttentionKey.ROLL_UP); + put(new KeyEventMap(0, KeyEvent.VK_PAGE_UP), AttentionKey.ROLL_DN); + put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F1), AttentionKey.PA1); + put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F2), AttentionKey.PA2); + put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F3), AttentionKey.PA3); + put(new KeyEventMap(KeyEvent.ALT_MASK, KeyEvent.VK_INSERT), AttentionKey.PA1); + put(new KeyEventMap(KeyEvent.ALT_MASK, KeyEvent.VK_HOME), AttentionKey.PA2); + put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_PAGE_UP), AttentionKey.PA3); + } + }; + private static final int SECRET_CREDENTIAL_ATTR = 39; + private static final Logger LOG = LoggerFactory.getLogger(XI5250CrtBase.class); + protected Map labelMap = new HashMap<>(); + protected T terminalClient; + protected StatusPanel statusPanel; + protected boolean locked = false; + protected boolean copyPaste = false; + protected Consumer pasteConsumer; + private List terminalEmulatorListeners; + private String sampleName = ""; + private Set supportedAttentionKeys; + + protected abstract List getInputFields(); + + @Override + protected synchronized void processKeyEvent(KeyEvent e) { + LOG.debug("Processing key: ({})", e); + AttentionKey attentionKey = null; + processCopyOrPaste(e); + if (isAnyKeyPressedOrControlKeyReleasedAndNotCopy(e)) { + attentionKey = KEY_EVENTS + .get(new KeyEventMap(e.getModifiers(), e.getKeyCode())); + if (attentionKey != null) { + processAttentionKey(e, attentionKey); + } + } + processBackSpace(e, attentionKey); + } + + private void processBackSpace(KeyEvent e, AttentionKey attentionKey) { + if ((!locked && !e.isConsumed()) || attentionKey != null) { + /* + By default XI5250Crt only move the cursor when the backspace key is + pressed and delete when shift mask is enabled, in this way always delete + */ + if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) { + super.processKeyEvent( + new KeyEvent(e.getComponent(), e.getID(), e.getWhen(), KeyEvent.SHIFT_MASK, + e.getKeyCode(), e.getKeyChar(), e.getKeyLocation())); + } else { + super.processKeyEvent(e); + } + statusPanel + .updateStatusBarCursorPosition(this.getCursorRow() + 1, this.getCursorCol() + 1); + } + } + + protected void processAttentionKey(KeyEvent e, AttentionKey attentionKey) { + if (isAttentionKeyValid(attentionKey)) { + List fields = getInputFields(); + for (TerminalEmulatorListener listener : terminalEmulatorListeners) { + setKeyboardLock(true); + listener.onAttentionKey(attentionKey, fields, sampleName); + } + } else { + showUserMessage(attentionKey + " not supported for current protocol"); + e.consume(); + } + + } + + protected void processCopyOrPaste(KeyEvent e) { + if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_C + && e.getModifiers() == getMenuShortcutKeyMask()) { + doCopy(); + e.consume(); + copyPaste = true; + } else if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_V + && e.getModifiers() == getMenuShortcutKeyMask() && !locked) { + makePaste(); + e.consume(); + copyPaste = true; + } else if (e.getID() == KeyEvent.KEY_RELEASED && + e.getKeyCode() == getKeyCodeFromKeyMask(getMenuShortcutKeyMask()) && copyPaste) { + copyPaste = false; + } + } + + private int getMenuShortcutKeyMask() { + return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + } + + private int getKeyCodeFromKeyMask(int keyMask) { + return keyMask == KeyEvent.META_MASK ? KeyEvent.VK_META : KeyEvent.VK_CONTROL; + } + + /* + This is implemented in this way because ctrl key is used in a shortcut to + copy/paste and the attention key should be triggered only if have not + executed a copy paste before. + */ + + protected boolean isAnyKeyPressedOrControlKeyReleasedAndNotCopy(KeyEvent e) { + return (e.getID() == KeyEvent.KEY_RELEASED + && e.getKeyCode() == getKeyCodeFromKeyMask(getMenuShortcutKeyMask()) && !copyPaste) + || (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() != getKeyCodeFromKeyMask( + getMenuShortcutKeyMask())); + } + + protected void showUserMessage(String msg) { + JOptionPane.showMessageDialog(this, msg, "Info", JOptionPane.INFORMATION_MESSAGE); + } + + @Override + public synchronized void paintComponent(Graphics g) { + super.paintComponent(g); + } + + public abstract void setKeyboardLock(boolean lock); + + protected boolean isAttentionKeyValid(AttentionKey attentionKey) { + return supportedAttentionKeys.contains(attentionKey); + } + + public void setSupportedAttentionKeys(Set supportedAttentionKeys) { + this.supportedAttentionKeys = supportedAttentionKeys; + } + + public void setProtocolClient(T terminalClient) { + this.terminalClient = terminalClient; + } + + public void setTerminalEmulatorListeners( + List terminalEmulatorListeners) { + this.terminalEmulatorListeners = terminalEmulatorListeners; + } + + public void setSampleSame(String sampleName) { + this.sampleName = sampleName; + } + + public void saveLabelWithPosition(Position position, String label) { + labelMap.put(position, label); + } + + public void setStatusPanel(StatusPanel statusPanel) { + this.statusPanel = statusPanel; + } + + //super.doCopy() has protected visibility therefore is override + @Override + public synchronized void doCopy() { + super.doCopy(); + } + + //super.doPaste() has protected visibility therefore is override + @Override + public synchronized void doPaste() { + super.doPaste(); + } + + public abstract void makePaste(); + + public abstract void teardown(); + + public void setInitialCursorPos(int column, int row) { + this.setCursorPos(column, row); + } + + public synchronized void setScreen(Screen screen, boolean isShowCredential) { + Dimension screenSize = screen.getSize(); + setCrtSize(screenSize.width, screenSize.height); + clear(); + removeFields(); + for (Screen.Segment s : screen.getSegments()) { + int row = s.getStartPosition().getRow() - 1; + int column = s.getStartPosition().getColumn() - 1; + if (s.isEditable()) { + int attr = + !isShowCredential && s.isSecret() ? SECRET_CREDENTIAL_ATTR : DEFAULT_ATTR; + drawString("\u0001", column - 1, row, attr); + XI5250Field xi5250Field = new ScreenField(this, column, + row, s.getText().length(), attr); + + xi5250Field.setString(s.getText()); + xi5250Field.resetMDT(); + + addField(xi5250Field); + } else { + drawString(s.getText(), column, + row, DEFAULT_ATTR); + } + } + this.initAllFields(); + } + + protected static class KeyEventMap { + + private final int modifier; + + private final int specialKey; + + KeyEventMap(int modifier, int specialKey) { + this.modifier = modifier; + this.specialKey = specialKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeyEventMap that = (KeyEventMap) o; + return modifier == that.modifier && + specialKey == that.specialKey; + } + + @Override + public int hashCode() { + return Objects.hash(modifier, specialKey); + } + + } + + public void setPasteEnableConsumer(Consumer pasteConsumer) { + this.pasteConsumer = pasteConsumer; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulator.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulator.java index 27667c34..22069c88 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulator.java +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulator.java @@ -1,10 +1,8 @@ package com.blazemeter.jmeter.rte.recorder.emulator; import com.blazemeter.jmeter.rte.core.AttentionKey; -import com.blazemeter.jmeter.rte.core.CoordInput; -import com.blazemeter.jmeter.rte.core.Input; -import com.blazemeter.jmeter.rte.core.LabelInput; import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.RteProtocolClient; import com.blazemeter.jmeter.rte.core.Screen; import com.blazemeter.jmeter.rte.sampler.gui.SwingUtils; import com.helger.commons.annotation.VisibleForTesting; @@ -13,19 +11,15 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; -import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Toolkit; -import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.Set; import javax.swing.GroupLayout; import javax.swing.GroupLayout.Alignment; @@ -36,6 +30,8 @@ import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.LayoutStyle.ComponentPlacement; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import net.infordata.em.crt5250.XI5250Crt; import net.infordata.em.crt5250.XI5250Field; import org.apache.jmeter.util.JMeterUtils; @@ -46,74 +42,30 @@ public class Xtn5250TerminalEmulator extends JFrame implements TerminalEmulator { - private static final int SECRET_CREDENTIAL_ATTR = 39; - private static final int DEFAULT_ATTR = 32; - private static final Map KEY_EVENTS = - new HashMap() { - { - put(new KeyEventMap(0, KeyEvent.VK_F1), AttentionKey.F1); - put(new KeyEventMap(0, KeyEvent.VK_F2), AttentionKey.F2); - put(new KeyEventMap(0, KeyEvent.VK_F3), AttentionKey.F3); - put(new KeyEventMap(0, KeyEvent.VK_F4), AttentionKey.F4); - put(new KeyEventMap(0, KeyEvent.VK_F5), AttentionKey.F5); - put(new KeyEventMap(0, KeyEvent.VK_F6), AttentionKey.F6); - put(new KeyEventMap(0, KeyEvent.VK_F7), AttentionKey.F7); - put(new KeyEventMap(0, KeyEvent.VK_F8), AttentionKey.F8); - put(new KeyEventMap(0, KeyEvent.VK_F9), AttentionKey.F9); - put(new KeyEventMap(0, KeyEvent.VK_F10), AttentionKey.F10); - put(new KeyEventMap(0, KeyEvent.VK_F11), AttentionKey.F11); - put(new KeyEventMap(0, KeyEvent.VK_F12), AttentionKey.F12); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F1), AttentionKey.F13); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F2), AttentionKey.F14); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F3), AttentionKey.F15); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F4), AttentionKey.F16); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F5), AttentionKey.F17); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F6), AttentionKey.F18); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F7), AttentionKey.F19); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F8), AttentionKey.F20); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F9), AttentionKey.F21); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F10), AttentionKey.F22); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F11), AttentionKey.F23); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_F12), AttentionKey.F24); - put(new KeyEventMap(0, KeyEvent.VK_ENTER), AttentionKey.ENTER); - put(new KeyEventMap(0, KeyEvent.VK_ESCAPE), AttentionKey.ATTN); - put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F4), AttentionKey.CLEAR); - put(new KeyEventMap(0, KeyEvent.VK_PAUSE), AttentionKey.CLEAR); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_ESCAPE), AttentionKey.SYSRQ); - put(new KeyEventMap(0, KeyEvent.VK_CONTROL), AttentionKey.RESET); - put(new KeyEventMap(KeyEvent.CTRL_MASK, KeyEvent.VK_CONTROL), AttentionKey.RESET); - put(new KeyEventMap(0, KeyEvent.VK_PAGE_DOWN), AttentionKey.ROLL_UP); - put(new KeyEventMap(0, KeyEvent.VK_PAGE_UP), AttentionKey.ROLL_DN); - put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F1), AttentionKey.PA1); - put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F2), AttentionKey.PA2); - put(new KeyEventMap(KeyEvent.META_MASK, KeyEvent.VK_F3), AttentionKey.PA3); - put(new KeyEventMap(KeyEvent.ALT_MASK, KeyEvent.VK_INSERT), AttentionKey.PA1); - put(new KeyEventMap(KeyEvent.ALT_MASK, KeyEvent.VK_HOME), AttentionKey.PA2); - put(new KeyEventMap(KeyEvent.SHIFT_MASK, KeyEvent.VK_PAGE_UP), AttentionKey.PA3); - } - }; private static final String TITLE = "Recorder"; private static final Color BACKGROUND = Color.black; private static final int DEFAULT_FONT_SIZE = 14; private static final Logger LOG = LoggerFactory.getLogger(Xtn5250TerminalEmulator.class); - private JButton waitForTextButton = createIconButton("waitForTextButton", "waitForText.png"); - private JButton copyButton = createIconButton("copyButton", "copy.png"); - private JButton pasteButton = createIconButton("pasteButton", "paste.png"); - private JButton labelButton = createIconButton("labelButton", "inputByLabel.png"); - private JButton assertionButton = createIconButton("assertionButton", "assertion.png"); - private JLabel sampleNameLabel = new JLabel("Sample name: "); - private JTextField sampleNameField = SwingUtils + private final JButton waitForTextButton = SwingUtils.createIconButton("waitForTextButton", + "waitForText.png"); + private final JButton copyButton = SwingUtils.createIconButton("copyButton", "copy.png"); + private final JButton pasteButton = SwingUtils.createIconButton("pasteButton", "paste.png"); + private final JButton labelButton = SwingUtils + .createIconButton("labelButton", "inputByLabel.png"); + private final JButton assertionButton = SwingUtils + .createIconButton("assertionButton", "assertion.png"); + private final JLabel sampleNameLabel = new JLabel("Sample name: "); + private final JTextField sampleNameField = SwingUtils .createComponent("sampleNameField", new JTextField()); - private List terminalEmulatorListeners = new ArrayList<>(); - private boolean locked = false; + private final List terminalEmulatorListeners = new ArrayList<>(); private boolean shownCredentials = false; private boolean stopping; - private StatusPanel statusPanel = new StatusPanel(); - private XI5250Crt xi5250Crt = new CustomXI5250Crt(); - private Set supportedAttentionKeys; - private Map labelMap = new HashMap<>(); + private final StatusPanel statusPanel = new StatusPanel(); + private final XI5250CrtBase xi5250Crt; - public Xtn5250TerminalEmulator() { + public Xtn5250TerminalEmulator(XI5250CrtBase xi5250Crt) { + this.xi5250Crt = xi5250Crt; + xi5250Crt.setPasteEnableConsumer((isPaste) -> pasteButton.setEnabled((Boolean) isPaste)); xi5250Crt.setName("Terminal"); xi5250Crt.setDefBackground(BACKGROUND); xi5250Crt.setBlinkingCursor(true); @@ -123,7 +75,6 @@ public Xtn5250TerminalEmulator() { add(createToolsPanel(), BorderLayout.NORTH); add(xi5250Crt, BorderLayout.CENTER); add(statusPanel, BorderLayout.SOUTH); - addWindowListener(new WindowAdapter() { @Override public void windowOpened(WindowEvent e) { @@ -131,7 +82,6 @@ public void windowOpened(WindowEvent e) { xi5250Crt.setSize(testSize.width, testSize.height); pack(); xi5250Crt.requestFocus(); - } @Override @@ -146,14 +96,21 @@ public void windowClosed(WindowEvent e) { }); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); statusPanel.getShowCredentials().addMouseListener(buildOnShowCredentials()); + setButtonListeners(); + modificationSamplerListener(); + xi5250Crt.setStatusPanel(statusPanel); + validateComponentsForCharacterEmulator(); } - private static JButton createIconButton(String name, String iconResource) { - return SwingUtils.createComponent(name, new ThemedIconButton(iconResource)); + private void validateComponentsForCharacterEmulator() { + if (xi5250Crt instanceof CharacterBasedEmulator) { + statusPanel.setBlockedCursorVisible(); + labelButton.setVisible(false); + } } private MouseListener buildOnShowCredentials() { - return new MouseListener() { + return new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { shownCredentials = !shownCredentials; @@ -161,32 +118,13 @@ public void mouseClicked(MouseEvent e) { statusPanel.updateShowCredentials(shownCredentials); updateFieldsVisibility(); } - - @Override - public void mousePressed(MouseEvent e) { - - } - - @Override - public void mouseReleased(MouseEvent e) { - - } - - @Override - public void mouseEntered(MouseEvent e) { - - } - - @Override - public void mouseExited(MouseEvent e) { - - } }; } private void updateFieldsVisibility() { for (XI5250Field field : xi5250Crt.getFields()) { - int attr = !shownCredentials ? ((ScreenField) field).originalAttr : DEFAULT_ATTR; + int attr = !shownCredentials ? ((ScreenField) field).originalAttr + : XI5250CrtBase.DEFAULT_ATTR; updateFieldAttribute(field.getRow(), field.getCol(), attr); field.init(); } @@ -273,12 +211,13 @@ private Dimension calculateCrtDefaultSize() { @Override public void stop() { stopping = true; + xi5250Crt.teardown(); dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } @Override public void setCursor(int row, int col) { - xi5250Crt.setCursorPos(col - 1, row - 1); + xi5250Crt.setInitialCursorPos(col - 1, row - 1); this.statusPanel.updateStatusBarCursorPosition(row, col); } @@ -295,33 +234,12 @@ public String getScreen() { } @Override - public synchronized void setScreen(Screen screen, String screenName) { - Dimension screenSize = screen.getSize(); - setScreenSize(screenSize.width, screenSize.height); - xi5250Crt.clear(); - xi5250Crt.removeFields(); - for (Screen.Segment s : screen.getSegments()) { - int row = s.getStartPosition().getRow() - 1; - int column = s.getStartPosition().getColumn() - 1; - if (s.isEditable()) { - int attr = - !shownCredentials && s.isSecret() ? SECRET_CREDENTIAL_ATTR : DEFAULT_ATTR; - updateFieldAttribute(row, column, attr); - - XI5250Field xi5250Field = new ScreenField(xi5250Crt, column, - row, s.getText().length(), attr); - - xi5250Field.setString(s.getText()); - xi5250Field.resetMDT(); - - xi5250Crt.addField(xi5250Field); - } else { - xi5250Crt - .drawString(s.getText(), column, - row, DEFAULT_ATTR); - } - } - xi5250Crt.initAllFields(); + public void setScreen(Screen screen, String screenName) { + /* + setScreen delegated to XI5250CrtBase in order to proper synchronize setScreen, + paintComponent and processKeyEvent methods. + */ + xi5250Crt.setScreen(screen, shownCredentials); sampleNameField.setText(screenName); } @@ -338,280 +256,158 @@ public void soundAlarm() { @Override public void setKeyboardLock(boolean lock) { - this.locked = lock; - this.statusPanel.setKeyboardStatus(lock); + xi5250Crt.setKeyboardLock(lock); } @Override public void addTerminalEmulatorListener(TerminalEmulatorListener terminalEmulatorListener) { terminalEmulatorListeners.add(terminalEmulatorListener); + xi5250Crt.setTerminalEmulatorListeners(terminalEmulatorListeners); } @Override public void setSupportedAttentionKeys(Set supportedAttentionKeys) { - this.supportedAttentionKeys = supportedAttentionKeys; + xi5250Crt.setSupportedAttentionKeys(supportedAttentionKeys); } - private boolean isAttentionKeyValid(AttentionKey attentionKey) { - return supportedAttentionKeys.contains(attentionKey); + @Override + public void setProtocolClient(RteProtocolClient terminalClient) { + xi5250Crt.setProtocolClient(terminalClient); } - private List getInputFields() { - List fields = new ArrayList<>(); - - for (XI5250Field f : xi5250Crt.getFields()) { - if (f.isMDTOn()) { + private void setButtonListeners() { + copyButton.addActionListener(e -> { + xi5250Crt.doCopy(); + xi5250Crt.requestFocus(); + }); + pasteButton.addActionListener(e -> { + xi5250Crt.makePaste(); + xi5250Crt.requestFocus(); + }); + labelButton.addActionListener(e -> { + String labelText = xi5250Crt.getStringSelectedArea(); + if (labelText == null) { + warnUserOfNotScreenSelectedArea("input by label"); + } else if (labelText.contains("\n")) { + showUserMessage("Please try again selecting one row"); + LOG.warn( + "Input by label does not support multiple selected rows, " + + "please select just one row."); + } else if (labelText.trim().isEmpty()) { + showUserMessage("Please select a non empty or blank text \nto be used as input by label"); + LOG.warn( + "Selected text is composed only by spaces."); + } else { + XI5250Field field = xi5250Crt + .getNextFieldFromPos(xi5250Crt.getSelectedArea().x, xi5250Crt.getSelectedArea().y); - Position fieldPosition = new Position(f.getRow() + 1, f.getCol() + 1); - String label = labelMap.get(fieldPosition); - String trimmedInput = trimNulls(f.getString()); - if (label != null) { - fields.add(new LabelInput(label, trimmedInput)); + if (isFieldValid(field)) { + xi5250Crt.saveLabelWithPosition(new Position(field.getRow() + 1, field.getCol() + 1), + labelText.trim()); } else { - fields.add(new CoordInput(fieldPosition, trimmedInput)); + showUserMessage("No input fields found near to \"" + labelText + "\"."); + LOG.warn("No field was found after specified label {}", labelText); } - } - } - labelMap.clear(); - return fields; - } - private String trimNulls(String str) { - if (str.isEmpty()) { - return str; - } - int firstNotNull = 0; - while (str.charAt(firstNotNull) == '\u0000') { - firstNotNull++; - } - int lastNotNull = str.length() - 1; - while (str.charAt(lastNotNull) == '\u0000') { - lastNotNull--; - } - return str.substring(firstNotNull, lastNotNull + 1); - } - - private static class ScreenField extends XI5250Field { - - private int originalAttr; + } - private ScreenField(XI5250Crt aCrt, int aCol, int aRow, int aLen, int aAttr) { - super(aCrt, aCol, aRow, aLen, aAttr); - originalAttr = aAttr; - } - } + xi5250Crt.requestFocus(); + xi5250Crt.clearSelectedArea(); + }); - private static class KeyEventMap { + waitForTextButton.addActionListener(e -> { + String selectedText = xi5250Crt.getStringSelectedArea(); - private final int modifier; - private final int specialKey; + if (selectedText != null) { + for (TerminalEmulatorListener listener : terminalEmulatorListeners) { + listener.onWaitForText(selectedText); + } + } else { + warnUserOfNotScreenSelectedArea("wait text condition"); + } + xi5250Crt.requestFocus(); + xi5250Crt.clearSelectedArea(); + }); - KeyEventMap(int modifier, int specialKey) { - this.modifier = modifier; - this.specialKey = specialKey; - } + assertionButton.addActionListener(e -> { + String selectedText = xi5250Crt.getStringSelectedArea(); + if (selectedText != null) { + String assertionName = requestAssertionName(); + if (assertionName != null) { + Pattern pattern = JMeterUtils + .getPattern(Perl5Compiler.quotemeta(selectedText).replace("\\\n", ".*\\n.*")); - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; + for (TerminalEmulatorListener listener : terminalEmulatorListeners) { + listener + .onAssertionScreen(assertionName, pattern.getPattern()); + } + } + } else { + warnUserOfNotScreenSelectedArea("assertion"); } - KeyEventMap that = (KeyEventMap) o; - return modifier == that.modifier && - specialKey == that.specialKey; - } - - @Override - public int hashCode() { - return Objects.hash(modifier, specialKey); - } + xi5250Crt.requestFocus(); + xi5250Crt.clearSelectedArea(); + }); } - private class CustomXI5250Crt extends XI5250Crt { - - private boolean copyPaste = false; - - private CustomXI5250Crt() { - copyButton.addActionListener(e -> { - doCopy(); - xi5250Crt.requestFocus(); - }); - pasteButton.addActionListener(e -> { - doPaste(); - xi5250Crt.requestFocus(); - }); - labelButton.addActionListener(e -> { - String labelText = xi5250Crt.getStringSelectedArea(); - if (labelText == null) { - warnUserOfNotScreenSelectedArea("input by label"); - } else if (labelText.contains("\n")) { - showUserMessage("Please try again selecting one row"); - LOG.warn( - "Input by label does not support multiple selected rows, " - + "please select just one row."); - } else if (labelText.trim().isEmpty()) { - showUserMessage("Please select a non empty or blank text \nto be used as input by label"); - LOG.warn( - "Selected text is composed only by spaces."); - } else { - XI5250Field field = xi5250Crt - .getNextFieldFromPos(xi5250Crt.getSelectedArea().x, xi5250Crt.getSelectedArea().y); - - if (isFieldValid(field)) { - labelMap.put(new Position(field.getRow() + 1, field.getCol() + 1), labelText.trim()); - } else { - showUserMessage("No input fields found near to \"" + labelText + "\"."); - LOG.warn("No field was found after specified label {}", labelText); - } + private boolean isFieldValid(XI5250Field field) { + int width = xi5250Crt.getCrtSize().width; + int linearLabelPosition = width * xi5250Crt.getSelectedArea().y + xi5250Crt + .getSelectedArea().x; + int linearFieldPosition = width * field.getRow() + field.getCol(); + return linearFieldPosition > linearLabelPosition; + } - } + private void warnUserOfNotScreenSelectedArea(String usage) { + showUserMessage("Please select a part of the screen"); + LOG.warn( + "The selection of a screen area is essential to " + + "be used as {} later on.", usage); + } - xi5250Crt.requestFocus(); - xi5250Crt.clearSelectedArea(); - }); + private void showUserMessage(String msg) { + JOptionPane.showMessageDialog(this, msg, "Info", JOptionPane.INFORMATION_MESSAGE); + } - waitForTextButton.addActionListener(e -> { - String selectedText = xi5250Crt.getStringSelectedArea(); + private String requestAssertionName() { + return JOptionPane.showInputDialog(this, "Insert name of assertion", "Response Assertion"); + } - if (selectedText != null) { - for (TerminalEmulatorListener listener : terminalEmulatorListeners) { - listener.onWaitForText(selectedText); + private void modificationSamplerListener() { + sampleNameField.getDocument().addDocumentListener( + new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + xi5250Crt.setSampleSame(sampleNameField.getText()); } - } else { - warnUserOfNotScreenSelectedArea("wait text condition"); - } - xi5250Crt.requestFocus(); - xi5250Crt.clearSelectedArea(); - }); - - assertionButton.addActionListener(e -> { - String selectedText = xi5250Crt.getStringSelectedArea(); - if (selectedText != null) { - String assertionName = requestAssertionName(); - if (assertionName != null) { - Pattern pattern = JMeterUtils - .getPattern(Perl5Compiler.quotemeta(selectedText).replace("\\\n", ".*\\n.*")); - - for (TerminalEmulatorListener listener : terminalEmulatorListeners) { - listener - .onAssertionScreen(assertionName, pattern.getPattern()); - } - } - } else { - warnUserOfNotScreenSelectedArea("assertion"); - } - xi5250Crt.requestFocus(); - xi5250Crt.clearSelectedArea(); - }); - } - - private boolean isFieldValid(XI5250Field field) { - int width = xi5250Crt.getCrtSize().width; - int linearLabelPosition = width * xi5250Crt.getSelectedArea().y + xi5250Crt - .getSelectedArea().x; - int linearFieldPosition = width * field.getRow() + field.getCol(); - return linearFieldPosition > linearLabelPosition; - } - private void showUserMessage(String msg) { - JOptionPane.showMessageDialog(this, msg, "Info", JOptionPane.INFORMATION_MESSAGE); - } + @Override + public void removeUpdate(DocumentEvent e) { - private void warnUserOfNotScreenSelectedArea(String usage) { - showUserMessage("Please select a part of the screen"); - LOG.warn( - "The selection of a screen area is essential to " - + "be used as {} later on.", usage); - } + xi5250Crt.setSampleSame(sampleNameField.getText()); + } - private String requestAssertionName() { - return JOptionPane.showInputDialog(this, "Insert name of assertion", "Response Assertion"); - } + @Override + public void changedUpdate(DocumentEvent e) { - @Override - protected synchronized void processKeyEvent(KeyEvent e) { - AttentionKey attentionKey = null; - if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_C - && e.getModifiers() == getMenuShortcutKeyMask()) { - doCopy(); - e.consume(); - copyPaste = true; - } else if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_V - && e.getModifiers() == getMenuShortcutKeyMask() && !locked) { - doPaste(); - e.consume(); - copyPaste = true; - } else if (e.getID() == KeyEvent.KEY_RELEASED && - e.getKeyCode() == getKeyCodeFromKeyMask(getMenuShortcutKeyMask()) && copyPaste) { - copyPaste = false; - } else if (isAnyKeyPressedOrControlKeyReleasedAndNotCopy(e)) { - attentionKey = KEY_EVENTS - .get(new KeyEventMap(e.getModifiers(), e.getKeyCode())); - - if (attentionKey != null) { - if (isAttentionKeyValid(attentionKey)) { - List fields = getInputFields(); - for (TerminalEmulatorListener listener : terminalEmulatorListeners) { - listener.onAttentionKey(attentionKey, fields, sampleNameField.getText()); - } - } else { - showUserMessage(attentionKey + " not supported for current protocol"); - e.consume(); + xi5250Crt.setSampleSame(sampleNameField.getText()); } } - } - if ((!locked && !e.isConsumed()) || attentionKey != null) { - //By default XI5250Crt only move the cursor when the backspace key is pressed and delete - // when shift mask is enabled, in this way always delete - if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) { - super.processKeyEvent( - new KeyEvent(e.getComponent(), e.getID(), e.getWhen(), KeyEvent.SHIFT_MASK, - e.getKeyCode(), e.getKeyChar(), e.getKeyLocation())); - } else { - super.processKeyEvent(e); - } - statusPanel - .updateStatusBarCursorPosition(this.getCursorRow() + 1, this.getCursorCol() + 1); - } - - } + ); + } - private int getMenuShortcutKeyMask() { - return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); - } + @VisibleForTesting + public List getInputs() { + return xi5250Crt.getInputFields(); + } - private int getKeyCodeFromKeyMask(int keyMask) { - if (keyMask == KeyEvent.META_MASK) { - return KeyEvent.VK_META; - } else { - return KeyEvent.VK_CONTROL; - } - } - //This is implemented in this way because ctrl key is used in a shortcut to - //copy/paste and the attention key should be triggered only if have not - //executed a copy paste before. - - private boolean isAnyKeyPressedOrControlKeyReleasedAndNotCopy(KeyEvent e) { - return (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() != getKeyCodeFromKeyMask( - getMenuShortcutKeyMask())) || (e.getID() == KeyEvent.KEY_RELEASED - && e.getKeyCode() == getKeyCodeFromKeyMask(getMenuShortcutKeyMask()) && !copyPaste); - } + public static class ScreenField extends XI5250Field { - @Override - protected void processMouseEvent(MouseEvent e) { - super.processMouseEvent(e); - statusPanel - .updateStatusBarCursorPosition(this.getCursorRow() + 1, this.getCursorCol() + 1); - } + private final int originalAttr; - @Override - public void paintComponent(Graphics g) { - synchronized (Xtn5250TerminalEmulator.this) { - super.paintComponent(g); - } + public ScreenField(XI5250Crt aCrt, int aCol, int aRow, int aLen, int aAttr) { + super(aCrt, aCol, aRow, aLen, aAttr); + originalAttr = aAttr; } } diff --git a/src/main/java/com/blazemeter/jmeter/rte/recorder/wait/SyncWaitRecorder.java b/src/main/java/com/blazemeter/jmeter/rte/recorder/wait/SyncWaitRecorder.java index 7fce5d3b..01d13f1e 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/recorder/wait/SyncWaitRecorder.java +++ b/src/main/java/com/blazemeter/jmeter/rte/recorder/wait/SyncWaitRecorder.java @@ -28,7 +28,7 @@ public SyncWaitRecorder(RteProtocolClient rteProtocolClient, long timeoutThresho @Override public void onTerminalStateChange() { - boolean inputInhibited = rteProtocolClient.isInputInhibited(); + boolean inputInhibited = rteProtocolClient.isInputInhibited().orElse(false); if (lastInputInhibited != inputInhibited) { lastInputInhibited = inputInhibited; hasUnlockedKeyboard = hasUnlockedKeyboard || !inputInhibited; @@ -41,7 +41,8 @@ public void onTerminalStateChange() { @Override public Optional buildWaitCondition() { - if (rteProtocolClient.isInputInhibited()) { + if (rteProtocolClient.isInputInhibited().isPresent() + && rteProtocolClient.isInputInhibited().get()) { return Optional.empty(); } if (maxStablePeriodMillis > stablePeriodMillis) { @@ -59,7 +60,7 @@ public Optional buildWaitCondition() { @Override public void start() { super.start(); - lastInputInhibited = rteProtocolClient.isInputInhibited(); + lastInputInhibited = rteProtocolClient.isInputInhibited().orElse(false); hasUnlockedKeyboard = false; } diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/NavigationInputRowGui.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/NavigationInputRowGui.java new file mode 100644 index 00000000..c5bcc01c --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/NavigationInputRowGui.java @@ -0,0 +1,88 @@ +package com.blazemeter.jmeter.rte.sampler; + +import com.blazemeter.jmeter.rte.core.Input; +import com.blazemeter.jmeter.rte.core.NavigationInput; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NavigationInputRowGui extends InputTestElement { + + private static final String REPEATED_COLUMN = "NavigationInputRowGui.repeated"; + private static final String NAVIGATION_TYPE = "NavigationInputRowGui.type"; + + public static NavigationInputRowGui parse(String text, String input) + throws IllegalArgumentException { + Optional currentType = Arrays.stream(NavigationType.values()) + .filter(navT -> text.toUpperCase().contains(navT.toString())) + .findFirst(); + + if (currentType.isPresent()) { + return parseNavigationInputFromText(text.toUpperCase(), input, currentType.get()); + } + throw new IllegalArgumentException( + "Given text does not match with any Navigation Input format type."); + } + + private static NavigationInputRowGui parseNavigationInputFromText(String text, String input, + NavigationType prefix) { + + Pattern pattern = Pattern.compile("<" + prefix + ">"); + Pattern extendedPattern = Pattern.compile("([" + pattern + "]+)"); + Pattern shortedPattern = Pattern.compile("<" + prefix + "\\*(\\d+)>"); + + Matcher shortVersionMatcher = shortedPattern.matcher(text); + Matcher extendedVersionMatcher = extendedPattern.matcher(text); + + NavigationInputRowGui navigationInputRowGui = new NavigationInputRowGui(); + navigationInputRowGui.setInput(input); + navigationInputRowGui.setType(prefix.getLabel()); + if (shortVersionMatcher.matches()) { + navigationInputRowGui.setRepeated(shortVersionMatcher.group(1)); + return navigationInputRowGui; + } else if (extendedVersionMatcher.matches()) { + Matcher helperNavMatcher = pattern.matcher(extendedVersionMatcher.group(1)); + int repeated = 0; + while (helperNavMatcher.find()) { + repeated++; + } + navigationInputRowGui.setRepeated(String.valueOf(repeated)); + return navigationInputRowGui; + } + throw new IllegalArgumentException( + "Given text does not match the established pattern for navigation inputs"); + } + + @Override + public Input toInput() { + return new NavigationInput(Integer.parseInt(getRepeated()), getTypeNavigation(), getInput()); + } + + @Override + public void copyOf(InputTestElement cellValue) { + if (cellValue instanceof NavigationInputRowGui) { + NavigationInputRowGui source = (NavigationInputRowGui) cellValue; + setRepeated(source.getRepeated()); + setInput(source.getInput()); + setType(source.getTypeNavigation().getLabel()); + } + } + + public String getRepeated() { + return getPropertyAsString(REPEATED_COLUMN, "1"); + } + + public void setRepeated(String repeated) { + setProperty(REPEATED_COLUMN, repeated); + } + + public void setType(String type) { + setProperty(NAVIGATION_TYPE, type); + } + + public NavigationType getTypeNavigation() { + return NavigationType.fromLabel(getPropertyAsString(NAVIGATION_TYPE, + NavigationType.TAB.getLabel())); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/NavigationType.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/NavigationType.java new file mode 100644 index 00000000..16990c75 --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/NavigationType.java @@ -0,0 +1,29 @@ +package com.blazemeter.jmeter.rte.sampler; + +import java.util.Arrays; + +public enum NavigationType { + TAB("Tabulator"), + UP("Up Arrow"), + DOWN("Down Arrow"), + RIGHT("Right Arrow"), + LEFT("Left Arrow"); + + private final String label; + + NavigationType(String label) { + this.label = label; + } + + public static NavigationType fromLabel(String label) { + return Arrays.stream(NavigationType.values()) + .filter(t -> t.getLabel().equals(label)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Label \'" + label + "\' does not " + + "match with any Navigation Type.")); + } + + public String getLabel() { + return label; + } +} diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/RTESampler.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/RTESampler.java index f4a4aebd..c295ca66 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/RTESampler.java +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/RTESampler.java @@ -4,6 +4,7 @@ import com.blazemeter.jmeter.rte.core.CoordInput; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import com.blazemeter.jmeter.rte.core.Position; import com.blazemeter.jmeter.rte.core.Protocol; import com.blazemeter.jmeter.rte.core.RteProtocolClient; @@ -98,8 +99,11 @@ public class RTESampler extends AbstractSampler implements ThreadListener, LoopI private static final String WAIT_TEXT_AREA_BOTTOM_PROPERTY = "RTESampler.waitTextAreaBottom"; private static final String WAIT_TEXT_AREA_RIGHT_PROPERTY = "RTESampler.waitTextAreaRight"; private static final String WAIT_TEXT_TIMEOUT_PROPERTY = "RTESampler.waitTextTimeout"; + private static final String CONFIG_CHARACTER_TIMEOUT = "RTEConnectionConfig" + + ".characterTimeoutMillis"; private static final Logger LOG = LoggerFactory.getLogger(RTESampler.class); + private static final long DEFAULT_CHARACTER_TIMEOUT_MILLIS = 60000; private static ThreadLocal> connections = ThreadLocal .withInitial(HashMap::new); @@ -453,6 +457,13 @@ private InputTestElement buildInputTestElement(Input input) { ret.setLabel(labelInput.getLabel()); ret.setInput(labelInput.getInput()); return ret; + } else if (input instanceof NavigationInput) { + NavigationInput navInput = (NavigationInput) input; + NavigationInputRowGui ret = new NavigationInputRowGui(); + ret.setRepeated(String.valueOf(navInput.getRepeat())); + ret.setInput(navInput.getInput()); + ret.setType(navInput.getNavigationType().getLabel()); + return ret; } else { throw new IllegalArgumentException("Unsupported input type " + input.getClass()); } @@ -486,10 +497,10 @@ public SampleResult sample(Entry entry) { try { if (getAction() == Action.SEND_INPUT) { - resultBuilder.withInputInhibitedRequest(client.isInputInhibited()) + resultBuilder.withInputInhibitedRequest(client.isInputInhibited().orElse(false)) .withAttentionKey(getAttentionKey()) .withInputs(getInputs()); - client.send(getInputs(), getAttentionKey()); + client.send(getInputs(), getAttentionKey(), getCharacterTimeout()); } List waiters = getWaitersList(); if (!waiters.isEmpty()) { @@ -517,6 +528,15 @@ public SampleResult sample(Entry entry) { return resultBuilder.build(); } + public static long getCharacterTimeout() { + return JMeterUtils.getPropDefault(CONFIG_CHARACTER_TIMEOUT, DEFAULT_CHARACTER_TIMEOUT_MILLIS); + } + + @VisibleForTesting + public static void setCharacterTimeout(long timeoutMillis) { + JMeterUtils.setProperty(CONFIG_CHARACTER_TIMEOUT, "" + timeoutMillis); + } + private RteSampleResultBuilder buildSampleResultBuilder() { return new RteSampleResultBuilder() .withLabel(getName()) diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputParser.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputParser.java deleted file mode 100644 index b37672bc..00000000 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputParser.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.blazemeter.jmeter.rte.sampler; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class TabulatorInputParser { - - private static final Pattern TAB_PATTERN = Pattern.compile(""); - private static final Pattern EXTENDED_VERSION_PATTERN = Pattern - .compile("([" + TAB_PATTERN.pattern() + "]+)"); - private static final Pattern SHORT_VERSION_PATTERN = Pattern.compile(""); - - public static TabulatorInputRowGui parse(String offset, String input) { - Matcher shortVersionMatcher = SHORT_VERSION_PATTERN.matcher(offset); - Matcher extendedVersionMatcher = EXTENDED_VERSION_PATTERN.matcher(offset); - - TabulatorInputRowGui tabulatorInputRowGui = new TabulatorInputRowGui(); - tabulatorInputRowGui.setInput(input); - - if (shortVersionMatcher.matches()) { - tabulatorInputRowGui.setOffset(shortVersionMatcher.group(1)); - return tabulatorInputRowGui; - } else if (extendedVersionMatcher.matches()) { - Matcher helperTabMatcher = TAB_PATTERN.matcher(extendedVersionMatcher.group(1)); - int tabOffset = 0; - while (helperTabMatcher.find()) { - tabOffset++; - } - tabulatorInputRowGui.setOffset(String.valueOf(tabOffset)); - return tabulatorInputRowGui; - } - throw new IllegalArgumentException( - "Given text does not match with any type of Tabulator Input format."); - } - -} diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputRowGui.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputRowGui.java index 4151daa7..aed37776 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputRowGui.java +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/TabulatorInputRowGui.java @@ -1,18 +1,21 @@ package com.blazemeter.jmeter.rte.sampler; import com.blazemeter.jmeter.rte.core.Input; -import com.blazemeter.jmeter.rte.core.TabulatorInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import org.apache.jmeter.testelement.property.StringProperty; +/* +This class has been deprecated since the addition of NavigationInput which is a more complex +structure that can store, what we used to call TabulatorInput and also the new arrows navigation. +This class is currently needed for the proper deserialization of older TestPlans. +Once all the older TestPlans migrate, this class will be eliminated. +*/ + +@Deprecated public class TabulatorInputRowGui extends InputTestElement { private static final String OFFSET_COLUMN = "TabulatorInputOffsetGUI.column"; - //provided for proper serialization - public TabulatorInputRowGui() { - - } - public String getOffset() { String val = getPropertyAsString(OFFSET_COLUMN, "1"); return val == null ? getPropertyAsString("TabulatorInputOffsetGUI.column") : val; @@ -24,7 +27,7 @@ public void setOffset(String offset) { @Override public Input toInput() { - return new TabulatorInput(Integer.valueOf(getOffset()), getInput()); + return new NavigationInput(Integer.valueOf(getOffset()), NavigationType.TAB, getInput()); } @Override diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanel.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanel.java index b631cad7..44713c3a 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanel.java +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanel.java @@ -4,9 +4,9 @@ import com.blazemeter.jmeter.rte.sampler.InputTestElement; import com.blazemeter.jmeter.rte.sampler.Inputs; import com.blazemeter.jmeter.rte.sampler.LabelInputRowGUI; -import com.blazemeter.jmeter.rte.sampler.TabulatorInputParser; +import com.blazemeter.jmeter.rte.sampler.NavigationInputRowGui; +import com.blazemeter.jmeter.rte.sampler.NavigationType; import com.blazemeter.jmeter.rte.sampler.TabulatorInputRowGui; -import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Rectangle; @@ -16,6 +16,7 @@ import java.awt.event.ActionListener; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; @@ -51,6 +52,7 @@ public class InputPanel extends JPanel implements ActionListener { private static final Logger LOG = LoggerFactory.getLogger(InputPanel.class); private static final String ADD_ACTION_LABEL = "addInputByLabel"; private static final String ADD_ACTION_POSITION = "addInputByPosition"; + private static final String ADD_ACTION_NAVIGATION = "addInputByNavigation"; private static final String ADD_ACTION = "addFromComboBox"; private static final String DELETE_ACTION = "delete"; private static final String UP_ACTION = "up"; @@ -59,9 +61,9 @@ public class InputPanel extends JPanel implements ActionListener { private static final String CLIPBOARD_ARG_DELIMITERS = "\t"; private static final String INPUT_BY_LABEL = "Input by Label"; private static final String INPUT_BY_POSITION = "Input by Position"; + private static final String INPUT_BY_NAVIGATION = "Input by Navigation"; private static final String FROM_CLIPBOARD = "From Clipboard"; - private static final String TAB_OFFSET = "Input by Tabulator"; - private static final String ADD_TABULATOR_OFFSET = "addTabulatorOffset"; + private InputTableModel tableModel; private JTable table; private JButton deleteButton; @@ -70,9 +72,7 @@ public class InputPanel extends JPanel implements ActionListener { private JComboBox comboType; public InputPanel() { - setLayout(new BorderLayout()); - add(makeMainPanel(), BorderLayout.CENTER); - add(makeButtonPanel(), BorderLayout.SOUTH); + initComponents(); table.revalidate(); } @@ -83,6 +83,34 @@ private static int getNumberOfVisibleRows(JTable table) { return table.rowAtPoint(vr.getLocation()) - first; } + private void initComponents() { + GroupLayout layout = new GroupLayout(this); + layout.setAutoCreateGaps(true); + setLayout(layout); + Component mainPanel = makeMainPanel(); + JPanel buttonPanel = makeButtonPanel(); + JLabel warningLabel = warningLabel(); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addComponent(mainPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(warningLabel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, + Short.MAX_VALUE) + .addComponent(buttonPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, + GroupLayout.PREFERRED_SIZE)); + + layout.setHorizontalGroup(layout.createParallelGroup() + .addComponent(mainPanel) + .addComponent(warningLabel) + .addComponent(buttonPanel)); + } + + private JLabel warningLabel() { + return SwingUtils + .createLabelWithWarningStyle("warningInputByLabel", "Warning: Input by Position and Input " + + "by Label are not supported for VT protocols"); + } + private Component makeMainPanel() { if (tableModel == null) { tableModel = new InputTableModel(new Object[]{"Field", "Value"}); @@ -106,37 +134,22 @@ protected String getText(Object value, int row, int column) { } private JPanel makeButtonPanel() { - - deleteButton = SwingUtils - .createComponent("deleteButton", new JButton(JMeterUtils.getResString("delete"))); - deleteButton.setActionCommand(DELETE_ACTION); - - upButton = SwingUtils.createComponent("upButton", new JButton(JMeterUtils.getResString("up"))); - upButton.setActionCommand(UP_ACTION); - - downButton = SwingUtils - .createComponent("downButton", new JButton(JMeterUtils.getResString("down"))); - downButton.setActionCommand(DOWN_ACTION); + deleteButton = SwingUtils.createButton("deleteButton", "delete", + DELETE_ACTION, this); + upButton = SwingUtils.createButton("upButton", "up", + UP_ACTION, this); + downButton = SwingUtils.createButton("downButton", "down", + DOWN_ACTION, this); + JButton addButton = SwingUtils.createButton("addButton", "Add", ADD_ACTION, this); comboType = SwingUtils.createComponent("comboType", new JComboBox()); - DefaultComboBoxModel model = new DefaultComboBoxModel(); - model.addElement(INPUT_BY_LABEL); - model.addElement(TAB_OFFSET); - model.addElement(INPUT_BY_POSITION); - model.addElement(FROM_CLIPBOARD); - comboType.setModel(model); - JButton addButton = SwingUtils.createComponent("addButton", new JButton("Add")); - addButton.setActionCommand(ADD_ACTION); + comboType.setModel(getDefaultComboBoxModel()); + updateEnabledButtons(); JPanel buttonPanel = SwingUtils.createComponent("buttonPanel", new JPanel()); buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); - - deleteButton.addActionListener(this); - upButton.addActionListener(this); - downButton.addActionListener(this); - addButton.addActionListener(this); buttonPanel.add(comboType); buttonPanel.add(addButton); buttonPanel.add(deleteButton); @@ -145,6 +158,15 @@ private JPanel makeButtonPanel() { return buttonPanel; } + private DefaultComboBoxModel getDefaultComboBoxModel() { + DefaultComboBoxModel model = new DefaultComboBoxModel(); + model.addElement(INPUT_BY_LABEL); + model.addElement(INPUT_BY_NAVIGATION); + model.addElement(INPUT_BY_POSITION); + model.addElement(FROM_CLIPBOARD); + return model; + } + private void updateEnabledButtons() { int rowCount = tableModel.getRowCount(); deleteButton.setEnabled(isEnabled() && rowCount != 0); @@ -167,6 +189,18 @@ public void configure(Inputs i) { tableModel.clearData(); for (JMeterProperty jMeterProperty : i) { InputTestElement input = (InputTestElement) jMeterProperty.getObjectValue(); + /* + Backward compatibility with those TestPlans which still + using TabulatorInput instead of NavigationInput. + Here we are also migrating to new format (NavigationInputRowGui) + */ + if (input instanceof TabulatorInputRowGui) { + NavigationInputRowGui navInput = new NavigationInputRowGui(); + navInput.setType(NavigationType.TAB.getLabel()); + navInput.setRepeated(((TabulatorInputRowGui) input).getOffset()); + navInput.setInput(input.getInput()); + input = navInput; + } tableModel.addRow(input); } } @@ -189,8 +223,8 @@ public void actionPerformed(ActionEvent e) { case FROM_CLIPBOARD: addFromClipboard(); break; - case TAB_OFFSET: - addArgument(ADD_TABULATOR_OFFSET); + case INPUT_BY_NAVIGATION: + addArgument(ADD_ACTION_NAVIGATION); break; default: throw new UnsupportedOperationException(selectedType); @@ -219,8 +253,8 @@ private void addArgument(String type) { tableModel.addRow(new CoordInputRowGUI()); } else if (ADD_ACTION_LABEL.equals(type)) { tableModel.addRow(new LabelInputRowGUI()); - } else if (ADD_TABULATOR_OFFSET.equals(type)) { - tableModel.addRow(new TabulatorInputRowGui()); + } else if (ADD_ACTION_NAVIGATION.equals(type)) { + tableModel.addRow(new NavigationInputRowGui()); } updateEnabledButtons(); @@ -276,29 +310,31 @@ private InputTestElement buildArgumentFromClipboard(String[] clipboardCols) { argument.setColumn(clipboardCols[1]); argument.setInput(clipboardCols[2]); return argument; - } - - if (clipboardCols.length == 2) { - String argument = clipboardCols[0] + "\t" + clipboardCols[1]; + } else { + String value = ""; + if (clipboardCols.length == 2) { + value = clipboardCols[1]; + } try { - LOG.info("Trying to build argument: '{}' as a Tabulator Input", - argument); - return TabulatorInputParser.parse(clipboardCols[0], clipboardCols[1]); + return NavigationInputRowGui.parse(clipboardCols[0], value); } catch (IllegalArgumentException e) { + if (value.isEmpty()) { + NavigationInputRowGui defaultArgument = new NavigationInputRowGui(); + defaultArgument.setRepeated("0"); + defaultArgument.setInput(clipboardCols[0]); + defaultArgument.setType(NavigationType.TAB.getLabel()); + return defaultArgument; + } LOG.info(e.getMessage()); - LOG.info("Building argument: '{}' as Input by Label", argument); + LOG.info("Building argument: '{}' as Input by Label", + clipboardCols[0] + " " + value); LabelInputRowGUI labelArgument = new LabelInputRowGUI(); labelArgument.setLabel(clipboardCols[0]); - labelArgument.setInput(clipboardCols[1]); + labelArgument.setInput(value); return labelArgument; } } - TabulatorInputRowGui defaultArgument = new TabulatorInputRowGui(); - defaultArgument.setOffset("0"); - defaultArgument.setInput(clipboardCols[0]); - return defaultArgument; - } private void deleteArgument() { @@ -401,12 +437,19 @@ protected static class FieldPanel extends JPanel { private JTextField fieldRow = SwingUtils.createComponent("fieldRow", new JTextField()); private JTextField fieldColumn = SwingUtils.createComponent("fieldColumn", new JTextField()); private JTextField fieldLabel = SwingUtils.createComponent("fieldLabel", new JTextField()); - private JTextField fieldTab = SwingUtils.createComponent("fieldTabulator", new JTextField()); + private JTextField fieldNavigation = SwingUtils + .createComponent("fieldNavigation", new JTextField()); + private JComboBox navigationCombo = SwingUtils.createComponent("navigationCombo", + new JComboBox()); + private JLabel navigationLabel = SwingUtils.createComponent("navigationLabel", new JLabel( + "Repeat: ")); + private DefaultComboBoxModel model = new DefaultComboBoxModel(); private FieldPanel() { layout = new GroupLayout(this); layout.setAutoCreateGaps(true); setLayout(layout); + setupNavigationCombo(); } private void updateFromField(InputTestElement value) { @@ -415,8 +458,8 @@ private void updateFromField(InputTestElement value) { buildPanel((CoordInputRowGUI) value); } else if (value instanceof LabelInputRowGUI) { buildPanel((LabelInputRowGUI) value); - } else if (value instanceof TabulatorInputRowGui) { - buildPanel((TabulatorInputRowGui) value); + } else if (value instanceof NavigationInputRowGui) { + buildPanel((NavigationInputRowGui) value); } } @@ -430,10 +473,11 @@ private void updateField(InputTestElement value) { LabelInputRowGUI input = (LabelInputRowGUI) value; input.setLabel(fieldLabel.getText()); } - if (value instanceof TabulatorInputRowGui) { - TabulatorInputRowGui input = (TabulatorInputRowGui) value; - String offset = verifyOffsetIntegrity(fieldTab.getText()); - input.setOffset(offset); + if (value instanceof NavigationInputRowGui) { + NavigationInputRowGui input = (NavigationInputRowGui) value; + String offset = verifyOffsetIntegrity(fieldNavigation.getText()); + input.setRepeated(offset); + input.setType((String) navigationCombo.getSelectedItem()); } } @@ -474,15 +518,27 @@ private void buildPanel(LabelInputRowGUI labelInputRowGUI) { .addComponent(fieldLabel)); } - private void buildPanel(TabulatorInputRowGui tabInputRowGui) { - label.setText("Tab offset"); - fieldTab.setText(tabInputRowGui.getOffset()); + private void buildPanel(NavigationInputRowGui navigationInputRowGui) { + label.setText("Navigation "); + fieldNavigation.setText(navigationInputRowGui.getRepeated()); + navigationCombo.setSelectedItem(navigationInputRowGui.getTypeNavigation().getLabel()); layout.setHorizontalGroup(layout.createSequentialGroup() .addComponent(label) - .addComponent(fieldTab)); + .addComponent(navigationCombo) + .addComponent(navigationLabel) + .addComponent(fieldNavigation)); layout.setVerticalGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(label) - .addComponent(fieldTab)); + .addComponent(navigationCombo) + .addComponent(navigationLabel) + .addComponent(fieldNavigation)); + } + + private void setupNavigationCombo() { + Arrays.stream(NavigationType.values()) + .map(NavigationType::getLabel) + .forEach(model::addElement); + navigationCombo.setModel(model); } } diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTEConfigPanel.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTEConfigPanel.java index 2a2eab8f..dd38332a 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTEConfigPanel.java +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTEConfigPanel.java @@ -30,6 +30,8 @@ public class RTEConfigPanel extends JPanel { buildTerminalTypesComboBoxModel(Protocol.TN5250); private static final DefaultComboBoxModel TN3270_TERMINAL_TYPES = buildTerminalTypesComboBoxModel(Protocol.TN3270); + private static final DefaultComboBoxModel VT420_TERMINAL_TYPES = + buildTerminalTypesComboBoxModel(Protocol.VT420); private ButtonGroup sslTypeGroup = new ButtonGroup(); private Map sslTypeRadios = new EnumMap<>(SSLType.class); @@ -148,6 +150,8 @@ private JComboBox buildProtocolComboBox() { terminalTypeComboBox.setModel(TN5250_TERMINAL_TYPES); } else if (protocolEnum.equals(Protocol.TN3270)) { terminalTypeComboBox.setModel(TN3270_TERMINAL_TYPES); + } else if (protocolEnum.equals(Protocol.VT420)) { + terminalTypeComboBox.setModel(VT420_TERMINAL_TYPES); } validate(); repaint(); diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTESamplerPanel.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTESamplerPanel.java index 0282b79b..640afd44 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTESamplerPanel.java +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/RTESamplerPanel.java @@ -27,6 +27,7 @@ public class RTESamplerPanel extends JPanel { private static final int INDEX_WIDTH = 30; private static final int TIME_WIDTH = 60; private static final String TIMEOUT_LABEL = "Timeout (millis): "; + private static final int LABEL_TABULATION_SPACE = 50; private final JPanel requestPanel; private final JPanel waitPanel; private ButtonGroup actionsGroup = new ButtonGroup(); @@ -138,16 +139,23 @@ private JPanel buildRequestPanel() { JPanel attentionKeysPanel = buildAttentionKeysPanel(); JLabel warningLabel = SwingUtils - .createComponent("warningLabel", new JLabel("Warning: AttentionKey buttons ATTN, " + - "RESET, ROLL_UP and ROLL_DN are only supported for TN5250 protocol. " + - "AttentionKey buttons PA1, PA2 and PA3 are only supported for TN3270 protocol.")); - warningLabel.setFont(new Font(null, Font.ITALIC, 11)); + .createLabelWithWarningStyle("warningLabel", "Warning: AttentionKey buttons ATTN and " + + "RESET are only supported for TN5250 protocol. " + + "AttentionKey buttons PA1, PA2 and PA3 are only supported for TN3270 protocol."); + JLabel warningLabelContinuation = SwingUtils + .createLabelWithWarningStyle("warningLabelContinuation", + "ROLL_UP and ROLL_DN are supported for VT420 and TN5250 protocols. VT420" + + " also supports ENTER and all function keys."); layout.setHorizontalGroup(layout.createParallelGroup() .addComponent(payloadLabel) .addComponent(payloadPanel) .addComponent(attentionKeysPanel) - .addComponent(warningLabel)); + .addComponent(warningLabel) + .addGroup(layout.createSequentialGroup() + .addGap(LABEL_TABULATION_SPACE) + .addComponent(warningLabelContinuation) + )); layout.setVerticalGroup(layout.createSequentialGroup() .addComponent(payloadLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, @@ -157,7 +165,9 @@ private JPanel buildRequestPanel() { .addComponent(attentionKeysPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(warningLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, - GroupLayout.PREFERRED_SIZE)); + GroupLayout.PREFERRED_SIZE) + .addComponent(warningLabelContinuation, GroupLayout.PREFERRED_SIZE, + GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)); return panel; } diff --git a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/SwingUtils.java b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/SwingUtils.java index d85609da..7df5c4b4 100644 --- a/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/SwingUtils.java +++ b/src/main/java/com/blazemeter/jmeter/rte/sampler/gui/SwingUtils.java @@ -1,8 +1,14 @@ package com.blazemeter.jmeter.rte.sampler.gui; +import com.blazemeter.jmeter.rte.recorder.emulator.ThemedIconButton; import java.awt.Component; import java.awt.Container; +import java.awt.Font; +import java.awt.event.ActionListener; +import javax.swing.JButton; import javax.swing.JComponent; +import javax.swing.JLabel; +import org.apache.jmeter.util.JMeterUtils; public class SwingUtils { @@ -19,4 +25,23 @@ public static void setEnabledRecursively(Component component, boolean enabled) { } } } + + public static JButton createButton(String name, String text, String action, + ActionListener listener) { + JButton button = SwingUtils + .createComponent(name, new JButton(JMeterUtils.getResString(text))); + button.setActionCommand(action); + button.addActionListener(listener); + return button; + } + + public static JButton createIconButton(String name, String iconResource) { + return SwingUtils.createComponent(name, new ThemedIconButton(iconResource)); + } + + public static JLabel createLabelWithWarningStyle(String name, String text) { + JLabel warningLabel = createComponent(name, new JLabel(text)); + warningLabel.setFont(warningLabel.getFont().deriveFont(Font.ITALIC, 11)); + return warningLabel; + } } diff --git a/src/main/resources/dark-theme/blocked-cursor.png b/src/main/resources/dark-theme/blocked-cursor.png new file mode 100644 index 00000000..c1ed54ab Binary files /dev/null and b/src/main/resources/dark-theme/blocked-cursor.png differ diff --git a/src/main/resources/dark-theme/cursor.png b/src/main/resources/dark-theme/cursor.png new file mode 100644 index 00000000..b7c474c2 Binary files /dev/null and b/src/main/resources/dark-theme/cursor.png differ diff --git a/src/main/resources/light-theme/blocked-cursor.png b/src/main/resources/light-theme/blocked-cursor.png new file mode 100644 index 00000000..b69599d7 Binary files /dev/null and b/src/main/resources/light-theme/blocked-cursor.png differ diff --git a/src/main/resources/light-theme/cursor.png b/src/main/resources/light-theme/cursor.png new file mode 100644 index 00000000..efed6817 Binary files /dev/null and b/src/main/resources/light-theme/cursor.png differ diff --git a/src/main/resources/recorder-help.html b/src/main/resources/recorder-help.html index 33569162..13fb970b 100644 --- a/src/main/resources/recorder-help.html +++ b/src/main/resources/recorder-help.html @@ -14,8 +14,9 @@

Keyboard mapping

Action Keys - Move cursor position - Arrows + + Move cursor position + Arrows Backspace @@ -112,9 +113,15 @@

Status bar

and is accompanied by an audio alert + + + This icon is displayed to advertise the user when current protocol does not support mouse + interactions. + + - Allow using a selected part of the screen as input by label + Allow using a selected part of the screen as input by label
NOTE: Input by label will be disable for VT protocols diff --git a/src/test/java/com/blazemeter/jmeter/rte/core/NavigationInputTest.java b/src/test/java/com/blazemeter/jmeter/rte/core/NavigationInputTest.java new file mode 100644 index 00000000..523f21cc --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/core/NavigationInputTest.java @@ -0,0 +1,60 @@ +package com.blazemeter.jmeter.rte.core; + +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.Collection; +import org.assertj.core.api.JUnitSoftAssertions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class NavigationInputTest { + + private static final Dimension screenSize = new Dimension(80, 24); + @Rule + public JUnitSoftAssertions softly = new JUnitSoftAssertions(); + @Parameter + public int currentRowPos; + @Parameter(1) + public int currentColumnPos; + @Parameter(2) + public int expectedRowPos; + @Parameter(3) + public int expectedColumnPos; + @Parameter(4) + public NavigationType navigationType; + @Parameter(5) + public int repeated; + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + {1, 1, 24, 80, NavigationType.LEFT, 1}, + {2, 1, 1, 80, NavigationType.LEFT, 1}, + {8, 54, 6, 53, NavigationType.LEFT, 161}, + {23, 80, 24, 1, NavigationType.RIGHT, 1}, + {24, 80, 1, 1, NavigationType.RIGHT, 1}, + {6, 53, 8, 54, NavigationType.RIGHT, 161}, + {1, 1, 24, 1, NavigationType.UP, 1}, + {5, 5, 1, 5, NavigationType.UP, 4}, + {1, 1, 2, 1, NavigationType.UP, 47}, + {1, 2, 2, 2, NavigationType.DOWN, 25}, + {24, 1, 1, 1, NavigationType.DOWN, 1}, + {2, 2, 6, 2, NavigationType.DOWN, 4} + }); + } + + @Test + public void shouldProperlyCalculateFinalPosition() { + NavigationInput input = new NavigationInput(repeated, navigationType, ""); + Position resultedPos = input.calculateInputFinalPosition(new Position(currentRowPos, + currentColumnPos), screenSize); + softly.assertThat(resultedPos.getRow()).isEqualTo(expectedRowPos); + softly.assertThat(resultedPos.getColumn()).isEqualTo(expectedColumnPos); + } +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilderTest.java b/src/test/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilderTest.java index f67ed6b0..133b7954 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilderTest.java +++ b/src/test/java/com/blazemeter/jmeter/rte/core/RteSampleResultBuilderTest.java @@ -49,7 +49,7 @@ private static Screen buildScreen() { public void setUp() { when(client.getScreen()).thenReturn(SCREEN); when(client.isAlarmOn()).thenReturn(true); - when(client.isInputInhibited()).thenReturn(true); + when(client.isInputInhibited()).thenReturn(Optional.of(true)); when(client.getCursorPosition()).thenReturn(Optional.of(CURSOR_POSITION)); } diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/RteProtocolClientIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/RteProtocolClientIT.java index e8b8c283..57d4a18a 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/protocols/RteProtocolClientIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/RteProtocolClientIT.java @@ -6,6 +6,7 @@ import com.blazemeter.jmeter.rte.core.TerminalType; import com.blazemeter.jmeter.rte.core.ssl.SSLType; import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; +import com.blazemeter.jmeter.rte.protocols.tn5250.Tn5250ClientIT; import com.google.common.base.Charsets; import com.google.common.io.Resources; import java.awt.Dimension; @@ -30,7 +31,7 @@ public abstract class RteProtocolClientIT { protected static final int TIMEOUT_MILLIS = 5000; protected static final int STABLE_TIMEOUT_MILLIS = 2000; protected static final long SERVER_STOP_TIMEOUT = TimeUnit.SECONDS.toMillis(10); - private static final Logger LOG = LoggerFactory.getLogger(RteProtocolClientIT.class); + private static final Logger LOG = LoggerFactory.getLogger(Tn5250ClientIT.class); protected VirtualTcpService server = new VirtualTcpService(); protected T client; @@ -80,8 +81,8 @@ protected void connectToVirtualService() throws Exception { protected Screen buildScreenFromHtmlFile(String fileName) throws IOException { return Screen.fromHtml(Resources.toString(findResource(fileName), Charsets.UTF_8)); } - + protected abstract List buildExpectedFields(); - - + + } diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270ClientIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270ClientIT.java index 2d50823b..1fb2735a 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270ClientIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/Tn3270ClientIT.java @@ -1,6 +1,7 @@ package com.blazemeter.jmeter.rte.protocols.tn3270; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.in; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -10,6 +11,7 @@ import com.blazemeter.jmeter.rte.core.CoordInput; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import com.blazemeter.jmeter.rte.core.Position; import com.blazemeter.jmeter.rte.core.Screen; import com.blazemeter.jmeter.rte.core.Screen.Segment; @@ -27,6 +29,7 @@ import com.blazemeter.jmeter.rte.core.wait.TextWaitCondition; import com.blazemeter.jmeter.rte.core.wait.WaitCondition; import com.blazemeter.jmeter.rte.protocols.RteProtocolClientIT; +import com.blazemeter.jmeter.rte.sampler.NavigationType; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -42,6 +45,8 @@ public class Tn3270ClientIT extends RteProtocolClientIT { + private static final String USERNAME = "testusr"; + @Override protected Tn3270Client buildClient() { return new Tn3270Client(); @@ -142,12 +147,12 @@ public void shouldGetUserMenuScreenWhenSendUsername() throws Exception { } private void sendUsernameWithSyncWait() throws Exception { - client.send(buildUsernameField(), AttentionKey.ENTER); + sendEnterAttentionKey(buildUsernameField()); awaitSync(); } private List buildUsernameField() { - return Collections.singletonList(new CoordInput(new Position(2, 1), "testusr")); + return Collections.singletonList(new CoordInput(new Position(2, 1), USERNAME)); } @Test @@ -165,7 +170,7 @@ private Screen buildLoginSuccessScreen() throws IOException { } private void sendPasswordByLabelWithSyncWait() throws Exception { - client.send(buildPasswordByLabel(), AttentionKey.ENTER); + client.send(buildPasswordByLabel(), AttentionKey.ENTER, 0); awaitSync(); } @@ -181,7 +186,7 @@ public void shouldThrowInvalidLabelExceptionWhenShowsIncorrectLabel() connectToVirtualService(); List input = Collections.singletonList( new LabelInput("Address", "address_Example_123")); - client.send(input, AttentionKey.ENTER); + client.send(input, AttentionKey.ENTER, 0); awaitSync(); } @@ -192,7 +197,7 @@ public void shouldThrowInvalidFieldPositionExceptionWhenSendIncorrectFieldPositi connectToVirtualService(); List input = Collections.singletonList( new CoordInput(new Position(81, 1), "TEST")); - client.send(input, AttentionKey.ENTER); + client.send(input, AttentionKey.ENTER, 0); } @Test(expected = RteIOException.class) @@ -222,26 +227,30 @@ public String getDescription() { public void shouldThrowTimeoutExceptionWhenSyncWaitAndSlowResponse() throws Exception { loadFlow("slow-response.yml"); connectToVirtualService(); - client.send(buildUsernameField(), AttentionKey.ENTER); + sendEnterAttentionKey(buildUsernameField()); awaitSync(); } + private void sendEnterAttentionKey(List inputs) throws RteIOException { + client.send(inputs, AttentionKey.ENTER, 0); + } + @Test(expected = TimeoutException.class) public void shouldThrowTimeoutExceptionWhenCursorWaitAndNotExpectedCursorPosition() throws Exception { loadLoginFlow(); connectToVirtualService(); - client.send(buildUsernameField(), AttentionKey.ENTER); + sendEnterAttentionKey(buildUsernameField()); client.await(Collections.singletonList( new CursorWaitCondition(new Position(1, - 51), TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + 50), TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); } @Test(expected = TimeoutException.class) public void shouldThrowTimeoutExceptionWhenSilentWaitAndChattyServer() throws Exception { loadFlow("chatty-server.yml"); connectToVirtualService(); - client.send(buildUsernameField(), AttentionKey.ENTER); + sendEnterAttentionKey(buildUsernameField()); client.await( Collections.singletonList(new SilentWaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); } @@ -251,7 +260,7 @@ public void shouldThrowTimeoutExceptionWhenTextWaitWithNoMatchingRegex() throws Exception { loadLoginFlow(); connectToVirtualService(); - client.send(buildUsernameField(), AttentionKey.ENTER); + sendEnterAttentionKey(buildUsernameField()); client.await(Collections .singletonList(new TextWaitCondition(new Perl5Compiler().compile("testing-wait-text"), new Perl5Matcher(), @@ -285,7 +294,7 @@ public void shouldThrowUnsupportedOperationExceptionWhenSelectAttentionKeyUnsupp throws Exception { loadFlow("login.yml"); connectToVirtualService(); - client.send(buildUsernameField(), AttentionKey.ROLL_UP); + client.send(buildUsernameField(), AttentionKey.ROLL_UP, 0); } @Test @@ -307,8 +316,8 @@ private void connectExtendedProtocolClientToVirtualService() public void shouldGetWelcomeScreenWhenLoginWithoutFields() throws Exception { loadFlow("login-without-fields.yml"); connectExtendedProtocolClientToVirtualService(); - client.send(Collections.singletonList(new CoordInput(new Position(20, 48), "testusr")), - AttentionKey.ENTER); + client.send(Collections.singletonList(new CoordInput(new Position(20, 48), USERNAME)), + AttentionKey.ENTER, 0); awaitSync(); assertThat(client.getScreen().withInvisibleCharsToSpaces()) .isEqualTo(buildScreenFromHtmlFile("login-without-fields-screen.html")); @@ -362,8 +371,25 @@ public void shouldThrowTimeoutExceptionWhenMatchedScreenChangedBeforeStablePerio loadFlow("login-with-multiple-flash-screen.yml"); client.connect(VIRTUAL_SERVER_HOST, server.getPort(), SSLType.NONE, getDefaultTerminalType(), TIMEOUT_MILLIS); - + client.await(Collections.singletonList(new TextWaitCondition(JMeterUtils.getPattern("AAAAA"), - JMeterUtils.getMatcher(), Area.fromTopLeftBottomRight(1, 1, Position.UNSPECIFIED_INDEX, Position.UNSPECIFIED_INDEX), TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + JMeterUtils.getMatcher(), + Area.fromTopLeftBottomRight(1, 1, Position.UNSPECIFIED_INDEX, Position.UNSPECIFIED_INDEX), + TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + } + + @Test + public void shouldGetUserMenuScreenWhenSendUserNameWithArrows() throws Exception { + loadFlow("login.yml"); + connectToVirtualService(); + List inputs = Arrays.asList( + new NavigationInput(1, NavigationType.DOWN, ""), + new NavigationInput(27, NavigationType.RIGHT, ""), + new NavigationInput(2, NavigationType.UP, ""), + new NavigationInput(1, NavigationType.LEFT, "testusr")); + sendEnterAttentionKey(inputs); + awaitSync(); + assertThat(client.getScreen().withInvisibleCharsToSpaces()) + .isEqualTo(buildScreenFromHtmlFile("user-menu-screen.html")); } } diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListenerIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListenerIT.java index 549847da..f07b8557 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListenerIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn3270/listeners/UnlockListenerIT.java @@ -6,6 +6,7 @@ import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; import com.bytezone.dm3270.application.KeyboardStatusChangedEvent; import com.google.common.base.Stopwatch; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.Before; @@ -13,10 +14,13 @@ public class UnlockListenerIT extends Tn3270ConditionWaiterIT { + private static final Optional FALSE = Optional.of(false); + private static final Optional TRUE = Optional.of(true); + @Override @Before public void setup() throws Exception { - when(client.isInputInhibited()).thenReturn(true); + when(client.isInputInhibited()).thenReturn(TRUE); super.setup(); } @@ -30,7 +34,7 @@ protected Tn3270ConditionWaiter buildConditionWaiter() { @Test public void shouldUnblockAfterReceivingUnlockStateChange() throws Exception { - when(client.isInputInhibited()).thenReturn(false); + when(client.isInputInhibited()).thenReturn(FALSE); KeyboardStatusChangedEvent keyboardEvent = new KeyboardStatusChangedEvent(false, false, ""); long unlockDelayMillis = 500; Stopwatch waitTime = Stopwatch.createStarted(); @@ -46,7 +50,7 @@ private Runnable buildKeyboardStateChangeGenerator(KeyboardStatusChangedEvent ke @Test public void shouldUnblockWhenAlreadyNotInputInhibited() throws Exception { - when(client.isInputInhibited()).thenReturn(false); + when(client.isInputInhibited()).thenReturn(FALSE); Tn3270ConditionWaiter listener = buildConditionWaiter(); listener.await(); } diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250ClientIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250ClientIT.java index 59cc0166..9649f07b 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250ClientIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/Tn5250ClientIT.java @@ -10,6 +10,7 @@ import com.blazemeter.jmeter.rte.core.CoordInput; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import com.blazemeter.jmeter.rte.core.Position; import com.blazemeter.jmeter.rte.core.Screen; import com.blazemeter.jmeter.rte.core.Screen.Segment; @@ -27,6 +28,7 @@ import com.blazemeter.jmeter.rte.core.wait.TextWaitCondition; import com.blazemeter.jmeter.rte.core.wait.WaitCondition; import com.blazemeter.jmeter.rte.protocols.RteProtocolClientIT; +import com.blazemeter.jmeter.rte.sampler.NavigationType; import java.io.FileNotFoundException; import java.util.Arrays; import java.util.Collections; @@ -96,8 +98,7 @@ public void shouldGetWelcomeScreenWhenConnectWithSsl() throws Exception { server.start(); client.connect(VIRTUAL_SERVER_HOST, server.getPort(), SSLType.TLS, getDefaultTerminalType(), TIMEOUT_MILLIS); - client.await( - Collections.singletonList(new SyncWaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + waitSync(); assertThat(client.getScreen().withInvisibleCharsToSpaces()) .isEqualTo(buildLoginWelcomeScreen()); } @@ -120,7 +121,11 @@ private Screen buildUserMenuScreen() throws java.io.IOException { } private void sendCredsByCoordWithSyncWait() throws Exception { - client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER); + client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER, 0); + waitSync(); + } + + private void waitSync() throws InterruptedException, TimeoutException, RteIOException { client.await( Collections.singletonList(new SyncWaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); } @@ -140,9 +145,8 @@ public void shouldGetUserMenuScreenWhenSendCredsByLabel() throws Exception { } private void sendCredsByLabelWithSyncWait() throws Exception { - client.send(buildCredsFieldsByLabel(), AttentionKey.ENTER); - client.await( - Collections.singletonList(new SyncWaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + client.send(buildCredsFieldsByLabel(), AttentionKey.ENTER, 0); + waitSync(); } private List buildCredsFieldsByLabel() { @@ -158,7 +162,7 @@ public void shouldThrowInvalidFieldPositionExceptionWhenSendIncorrectFieldPositi connectToVirtualService(); List input = Collections.singletonList( new CoordInput(new Position(7, 1), TEST_USERNAME)); - client.send(input, AttentionKey.ENTER); + client.send(input, AttentionKey.ENTER, 0); } @Test(expected = InvalidFieldLabelException.class) @@ -168,7 +172,7 @@ public void shouldThrowInvalidFieldPositionExceptionWhenSendIncorrectFieldPositi connectToVirtualService(); List input = Collections.singletonList( new LabelInput("Usr", TEST_USERNAME)); - client.send(input, AttentionKey.ENTER); + client.send(input, AttentionKey.ENTER, 0); } @Test @@ -213,9 +217,8 @@ public String getDescription() { public void shouldThrowTimeoutExceptionWhenSyncWaitAndSlowResponse() throws Exception { loadFlow("slow-response.yml"); connectToVirtualService(); - client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER); - client.await( - Collections.singletonList(new SyncWaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER, 0); + waitSync(); } @Test(expected = TimeoutException.class) @@ -223,7 +226,7 @@ public void shouldThrowTimeoutExceptionWhenCursorWaitAndNotExpectedCursorPositio throws Exception { loadLoginFlow(); connectToVirtualService(); - client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER); + client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER, 0); client.await(Collections.singletonList( new CursorWaitCondition(new Position(1, 1), TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); } @@ -232,7 +235,7 @@ public void shouldThrowTimeoutExceptionWhenCursorWaitAndNotExpectedCursorPositio public void shouldThrowTimeoutExceptionWhenSilentWaitAndChattyServer() throws Exception { loadFlow("chatty-server.yml"); connectToVirtualService(); - client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER); + client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER, 0); client.await( Collections.singletonList(new SilentWaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); } @@ -242,7 +245,7 @@ public void shouldThrowTimeoutExceptionWhenTextWaitWithNoMatchingRegex() throws Exception { loadLoginFlow(); connectToVirtualService(); - client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER); + client.send(buildCredsFieldsByCoord(), AttentionKey.ENTER, 0); client.await(Collections .singletonList(new TextWaitCondition(new Perl5Compiler().compile("testing-wait-text"), new Perl5Matcher(), @@ -276,7 +279,7 @@ public void shouldThrowUnsupportedOperationExceptionWhenSelectAttentionKeyUnsupp throws Exception { loadLoginFlow(); connectToVirtualService(); - client.send(buildCredsFieldsByCoord(), AttentionKey.PA1); + client.send(buildCredsFieldsByCoord(), AttentionKey.PA1, 0); } @Test @@ -333,4 +336,18 @@ public void shouldFailWhenMatchedScreenChangedBeforeStablePeriod() throws Except client.await(Collections.singletonList(new TextWaitCondition(JMeterUtils.getPattern("Sign on"), JMeterUtils.getMatcher(), Area.fromTopLeftBottomRight(1,1,24,80), 10000, 1000))); } + + @Test + public void shouldGetUserMenuScreenWhenSendNavigationArrowInputs() throws Exception { + loadFlow("login.yml"); + connectToVirtualService(); + List inputs = Arrays.asList( + new NavigationInput(0, NavigationType.DOWN, "TESTUSR"), + new NavigationInput(160, NavigationType.RIGHT, ""), + new NavigationInput(1, NavigationType.UP, ""), + new NavigationInput(7, NavigationType.LEFT, "TESTPSW")); + client.send(inputs, AttentionKey.ENTER, 0); + waitSync(); + assertThat(client.getScreen().withInvisibleCharsToSpaces()).isEqualTo(buildUserMenuScreen()); + } } diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListenerIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListenerIT.java index bf8fda21..611280ef 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListenerIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/tn5250/listeners/UnlockListenerIT.java @@ -5,6 +5,7 @@ import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; import com.google.common.base.Stopwatch; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.Before; @@ -14,10 +15,12 @@ public class UnlockListenerIT extends Tn5250ConditionWaiterIT { + private static final Optional TRUE = Optional.of(true); + private static final Optional FALSE = Optional.of(false); @Override @Before public void setup() throws Exception { - when(client.isInputInhibited()).thenReturn(true); + when(client.isInputInhibited()).thenReturn(TRUE); super.setup(); } @@ -31,7 +34,7 @@ protected Tn5250ConditionWaiter buildConditionWaiter() { @Test public void shouldUnblockAfterReceivingUnlockStateChange() throws Exception { - when(client.isInputInhibited()).thenReturn(false); + when(client.isInputInhibited()).thenReturn(FALSE); long unlockDelayMillis = 500; Stopwatch waitTime = Stopwatch.createStarted(); startSingleEventGenerator(unlockDelayMillis, buildStateChangeGenerator()); @@ -41,7 +44,7 @@ public void shouldUnblockAfterReceivingUnlockStateChange() throws Exception { @Test public void shouldUnblockWhenAlreadyNotInputInhibited() throws Exception { - when(client.isInputInhibited()).thenReturn(false); + when(client.isInputInhibited()).thenReturn(FALSE); Tn5250ConditionWaiter listener = buildConditionWaiter(); listener.await(); } diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/Vt420ClientIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/Vt420ClientIT.java new file mode 100644 index 00000000..d17fc46d --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/Vt420ClientIT.java @@ -0,0 +1,267 @@ +package com.blazemeter.jmeter.rte.protocols.vt420; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.blazemeter.jmeter.rte.JMeterTestUtils; +import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.Input; +import com.blazemeter.jmeter.rte.core.NavigationInput; +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.Screen.Segment; +import com.blazemeter.jmeter.rte.core.TerminalType; +import com.blazemeter.jmeter.rte.core.exceptions.RteIOException; +import com.blazemeter.jmeter.rte.core.listener.ExceptionHandler; +import com.blazemeter.jmeter.rte.core.ssl.SSLContextFactory; +import com.blazemeter.jmeter.rte.core.ssl.SSLType; +import com.blazemeter.jmeter.rte.core.wait.Area; +import com.blazemeter.jmeter.rte.core.wait.CursorWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.TextWaitCondition; +import com.blazemeter.jmeter.rte.core.wait.WaitCondition; +import com.blazemeter.jmeter.rte.protocols.RteProtocolClientIT; +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import com.blazemeter.jmeter.rte.sampler.RTESampler; +import java.awt.Dimension; +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeoutException; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.assertj.core.api.JUnitSoftAssertions; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class Vt420ClientIT extends RteProtocolClientIT { + + private static final String USER_ID = "tt"; + private static final String DATA = "123456"; + private static final String PASSWORD = "passwd"; + private static final NavigationInput USER_ID_INPUT = new NavigationInput(0, NavigationType.TAB, + USER_ID); + private static final NavigationInput DATA_INPUT = new NavigationInput(0, NavigationType.TAB, + DATA); + private static final NavigationInput USER_PASSWORD_INPUT = new NavigationInput(0, + NavigationType.TAB, PASSWORD); + private static final Position USER_ID_CURSOR_POSITION = new Position(12, 42); + private static final Position WELCOME_SCREEN_CURSOR_POSITION = new Position(12, 27); + private static final String LOGIN_SUCCESS_SCREEN_HTML = "login-success-screen.html"; + private static final String ARROW_NAVIGATION_SCREEN_HTML = "arrow-navigation-screen.html"; + @Rule + public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); + + @BeforeClass + public static void setupClass() { + JMeterTestUtils.setupJmeterEnv(); + } + + @Override + protected Vt420Client buildClient() { + return new Vt420Client(); + } + + @Override + protected TerminalType getDefaultTerminalType() { + return new TerminalType("VT420-7", new Dimension(80, 24)); + } + + @Override + public void setup() throws Exception { + super.setup(); + RTESampler.setCharacterTimeout(5000); + } + + @Override + protected List buildExpectedFields() { + return null; + } + + private void loadLoginFlow() throws FileNotFoundException { + loadFlow("login.yml"); + } + + @Test + public void shouldGetWelcomeScreenWhenConnect() throws Exception { + loadLoginFlow(); + connectToVirtualService(); + assertThat(client.getScreen()) + .isEqualTo(buildScreenFromHtmlFile("user-welcome-screen.html")); + } + + @Test(expected = RteIOException.class) + public void shouldThrowRteIOExceptionWhenConnectWithInvalidPort() throws Exception { + client.connect(VIRTUAL_SERVER_HOST, 1, SSLType.NONE, getDefaultTerminalType(), TIMEOUT_MILLIS); + } + + private void waitForCursorPosition(Position position) + throws InterruptedException, TimeoutException, RteIOException { + client.await(Collections.singletonList( + new CursorWaitCondition(position, TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS))); + } + + @Test + public void shouldGetUserMenuScreenWhenSendUsername() throws Exception { + loadLoginFlow(); + connectToVirtualService(); + waitForCursorPosition(WELCOME_SCREEN_CURSOR_POSITION); + sendEnterAttentionKey(); + waitForCursorPosition(USER_ID_CURSOR_POSITION); + assertThat(client.getScreen().withInvisibleCharsToSpaces()) + .isEqualTo(buildScreenFromHtmlFile("user-menu-screen.html")); + } + + private void sendEnterAttentionKey() throws RteIOException { + client.send(Collections.emptyList(), AttentionKey.ENTER, TIMEOUT_MILLIS); + } + + @Test + public void shouldGetArrownavigationScreenWhenSendCorrectCredentialsByTabulator() + throws Exception { + loadLoginFlow(); + connectToVirtualService(); + waitForCursorPosition(WELCOME_SCREEN_CURSOR_POSITION); + sendEnterAttentionKey(); + waitForCursorPosition(USER_ID_CURSOR_POSITION); + sendCredentialsByTabulatorWithSilent(); + assertThat(client.getScreen().withInvisibleCharsToSpaces()) + .isEqualTo(buildScreenFromHtmlFile(ARROW_NAVIGATION_SCREEN_HTML)); + + } + + private void sendCredentialsByTabulatorWithSilent() + throws RteIOException, TimeoutException, InterruptedException { + client.send(Arrays.asList(USER_ID_INPUT, DATA_INPUT, USER_PASSWORD_INPUT), AttentionKey.ENTER, + TIMEOUT_MILLIS); + client.await(Collections.singletonList(new SyncWaitCondition(3000, STABLE_TIMEOUT_MILLIS))); + } + + @Test(expected = RteIOException.class) + public void shouldThrowRteIOExceptionWhenSendAndServerDown() throws Exception { + loadLoginFlow(); + connectToVirtualService(); + server.stop(SERVER_STOP_TIMEOUT); + sendEnterAttentionKey(); + } + + @Test(expected = UnsupportedOperationException.class) + public void shouldThrowUnsupportedOperationExceptionWhenAwaitWithUndefinedCondition() + throws Exception { + loadLoginFlow(); + connectToVirtualService(); + List conditions = Collections + .singletonList(new WaitCondition(TIMEOUT_MILLIS, STABLE_TIMEOUT_MILLIS) { + @Override + public String getDescription() { + return "test"; + } + }); + client.await(conditions); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenCursorWaitAndNotExpectedCursorPosition() + throws Exception { + Position failingCursorPosition = new Position(1, 1); + loadLoginFlow(); + connectToVirtualService(); + waitForCursorPosition(WELCOME_SCREEN_CURSOR_POSITION); + sendEnterAttentionKey(); + waitForCursorPosition(failingCursorPosition); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenTextWaitWithNoMatchingRegex() + throws Exception { + loadLoginFlow(); + connectToVirtualService(); + waitForCursorPosition(WELCOME_SCREEN_CURSOR_POSITION); + sendEnterAttentionKey(); + client.await(Collections + .singletonList(new TextWaitCondition(new Perl5Compiler().compile("NOT-IN-SCREEN"), + new Perl5Matcher(), + Area.fromTopLeftBottomRight(1, 1, Position.UNSPECIFIED_INDEX, + Position.UNSPECIFIED_INDEX), + TIMEOUT_MILLIS, + STABLE_TIMEOUT_MILLIS))); + } + + @Test + public void shouldNotThrowExceptionWhenDisconnectAndServerDown() throws Exception { + loadLoginFlow(); + connectToVirtualService(); + server.stop(SERVER_STOP_TIMEOUT); + client.disconnect(); + } + + @Test(expected = UnsupportedOperationException.class) + public void shouldThrowUnsupportedOperationExceptionWhenSelectAttentionKeyUnsupported() + throws Exception { + loadLoginFlow(); + connectToVirtualService(); + client.send(Collections.emptyList(), AttentionKey.RESET, 0); + } + + @Test(expected = RteIOException.class) + public void shouldThrowTimeoutExceptionWhenNoScreenChangesAndSendText() throws Exception { + loadLoginFlow(); + connectToVirtualService(); + ExceptionHandler exceptionHandler = new ExceptionHandler(""); + client.setExceptionHandler(exceptionHandler); + waitForCursorPosition(WELCOME_SCREEN_CURSOR_POSITION); + client.send("E"); + exceptionHandler.throwAnyPendingError(); + } + + + @Test + public void shouldGetSuccessScreenWhenSendingInputsByNavigation() throws Exception { + loadLoginFlow(); + connectToVirtualService(); + waitForCursorPosition(WELCOME_SCREEN_CURSOR_POSITION); + sendEnterAttentionKey(); + waitForCursorPosition(USER_ID_CURSOR_POSITION); + sendCredentialsByTabulatorWithSilent(); + waitForCursorPosition(new Position(1, 54)); + sendArrowMovementsScreenDataWithSilentWaitCondition(); + assertThat(client.getScreen().withInvisibleCharsToSpaces()) + .isEqualTo(buildScreenFromHtmlFile("login-success-screen.html")); + } + + private void sendArrowMovementsScreenDataWithSilentWaitCondition() + throws RteIOException, TimeoutException, InterruptedException { + client.send(Arrays.asList( + new NavigationInput(1, NavigationType.DOWN, "00"), + new NavigationInput(2, NavigationType.LEFT, "dev"), + new NavigationInput(1, NavigationType.UP, "test"), + new NavigationInput(1, NavigationType.RIGHT, "root")), + AttentionKey.ENTER, + TIMEOUT_MILLIS); + awaitSync(); + } + + @Test + public void shouldGetWelcomeScreenWhenConnectWithSsl() throws Exception { + server.stop(SERVER_STOP_TIMEOUT); + loadLoginFlow(); + SSLContextFactory.setKeyStore(findResource("/.keystore").getFile()); + SSLContextFactory.setKeyStorePassword("changeit"); + server.setSslEnabled(true); + server.start(); + client.connect(VIRTUAL_SERVER_HOST, server.getPort(), SSLType.TLS, getDefaultTerminalType(), + TIMEOUT_MILLIS); + awaitSync(); + assertThat(client.getScreen().withInvisibleCharsToSpaces()) + .isEqualTo(buildScreenFromHtmlFile("user-welcome-screen.html")); + } + + private void awaitSync() throws InterruptedException, TimeoutException, RteIOException { + client.await(Collections.singletonList(new SyncWaitCondition(3000, STABLE_TIMEOUT_MILLIS))); + } +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/ScreenTextListenerIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/ScreenTextListenerIT.java new file mode 100644 index 00000000..c21a99c4 --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/ScreenTextListenerIT.java @@ -0,0 +1,100 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.Screen; +import com.blazemeter.jmeter.rte.core.exceptions.RteIOException; +import com.blazemeter.jmeter.rte.core.wait.Area; +import com.blazemeter.jmeter.rte.core.wait.TextWaitCondition; +import com.google.common.base.Stopwatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; +import org.apache.oro.text.regex.MalformedPatternException; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.junit.Before; +import org.junit.Test; + +public class ScreenTextListenerIT extends Vt420ConditionWaiterIT { + + private static final String EXPECTED_SCREEN = "welcome\n"; + + @Before + @Override + public void setup() throws Exception { + setupScreenWithText("GoodBye\n"); + super.setup(); + } + + private void setupScreenWithText(String screen) { + when(client.getScreen()).thenReturn(Screen.valueOf(screen)); + } + + @Override + protected Vt420ConditionWaiter buildConditionWaiter() throws Exception { + return buildTextListener(EXPECTED_SCREEN); + } + + private ScreenTextListener buildTextListener(String regex) throws MalformedPatternException { + return new ScreenTextListener( + new TextWaitCondition(new Perl5Compiler().compile(regex), new Perl5Matcher(), + Area.fromTopLeftBottomRight(1, 1, Position.UNSPECIFIED_INDEX, + Position.UNSPECIFIED_INDEX), TIMEOUT_MILLIS, STABLE_MILLIS), + client, + stableTimeoutExecutor, + exceptionHandler); + } + + @Test + public void shouldUnblockAfterReceivingScreenWithExpectedRegexInArea() throws Exception { + setupScreenWithText(EXPECTED_SCREEN); + long unlockDelayMillis = 500; + Stopwatch waitTime = Stopwatch.createStarted(); + startSingleEventGenerator(unlockDelayMillis, buildNewScreenChange()); + listener.await(); + assertThat(waitTime.elapsed(TimeUnit.MILLISECONDS)).isGreaterThanOrEqualTo(unlockDelayMillis); + } + + private Runnable buildNewScreenChange() { + return () -> ((ScreenChangeListener) listener).screenChanged(EXPECTED_SCREEN); + } + + @Test + public void shouldUnblockWhenScreenAlreadyContainsTextWithExpectedRegexInArea() throws Exception { + ScreenTextListener listener = buildTextListener("GoodBye"); + listener.await(); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenNoScreenReceivedMatchingRegexArea() + throws InterruptedException, TimeoutException, RteIOException { + listener.await(); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenReceivedScreenNotMatchingRegexArea() + throws Exception { + ScreenTextListener listener = buildTextListener(EXPECTED_SCREEN); + listener.await(); + } + + @Test + public void shouldThrowTimeoutExceptionWhenReceivedExpectedScreenButKeepGettingStateChanges() + throws Exception { + setupScreenWithText(EXPECTED_SCREEN); + buildNewScreenChange().run(); + startPeriodicEventGenerator(buildNewScreenChange()); + listener.await(); + } + + @Test + public void shouldThrowTimeoutExceptionWhenReceivedExpectedScreenButKeepGettingScreens() + throws Exception { + setupScreenWithText(EXPECTED_SCREEN); + startPeriodicEventGenerator(buildNewScreenChange()); + listener.await(); + } +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/SilenceListenerIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/SilenceListenerIT.java new file mode 100644 index 00000000..fd433485 --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/SilenceListenerIT.java @@ -0,0 +1,50 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.blazemeter.jmeter.rte.core.exceptions.RteIOException; +import com.blazemeter.jmeter.rte.core.wait.SilentWaitCondition; +import com.google.common.base.Stopwatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.Before; +import org.junit.Test; + +public class SilenceListenerIT extends Vt420ConditionWaiterIT { + + private Stopwatch waitTime; + + @Before + @Override + public void setup() throws Exception { + waitTime = Stopwatch.createStarted(); + super.setup(); + } + + @Override + protected Vt420ConditionWaiter buildConditionWaiter() { + return new SilenceListener(new SilentWaitCondition(TIMEOUT_MILLIS, STABLE_MILLIS), + client, + stableTimeoutExecutor, + exceptionHandler); + } + + @Test + public void shouldUnlockAfterSilentTimeWhenNoEvents() + throws InterruptedException, TimeoutException, RteIOException { + listener.await(); + assertThat(waitTime.elapsed(TimeUnit.MILLISECONDS)).isGreaterThanOrEqualTo(STABLE_MILLIS); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenKeepReceivingScreenChanges() + throws Exception { + startPeriodicEventGenerator(buildScreenStateChangeGenerator()); + listener.await(); + } + + private Runnable buildScreenStateChangeGenerator() { + return () -> ((SilenceListener) listener) + .screenChanged(""); + } +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/UnlockListenerIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/UnlockListenerIT.java new file mode 100644 index 00000000..aec0c200 --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/UnlockListenerIT.java @@ -0,0 +1,57 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.blazemeter.jmeter.rte.core.exceptions.RteIOException; +import com.blazemeter.jmeter.rte.core.wait.SyncWaitCondition; +import com.google.common.base.Stopwatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.Before; +import org.junit.Test; + +/* + Nowadays this class is covered with same tests as SilenceListener + due to behaviour of unlockListener (which is similar to silence). + Tests will be solved after JIRA ticket: RTE-124 + */ + +public class UnlockListenerIT extends Vt420ConditionWaiterIT { + + private Stopwatch waitTime; + + @Before + @Override + public void setup() throws Exception { + waitTime = Stopwatch.createStarted(); + super.setup(); + } + + @Override + protected Vt420ConditionWaiter buildConditionWaiter() { + return new UnlockListener( + new SyncWaitCondition(TIMEOUT_MILLIS, STABLE_MILLIS), + client, + stableTimeoutExecutor, + exceptionHandler); + } + + @Test + public void shouldUnlockAfterStablePeriodLapseWhenNoEvents() + throws InterruptedException, TimeoutException, RteIOException { + listener.await(); + assertThat(waitTime.elapsed(TimeUnit.MILLISECONDS)).isGreaterThanOrEqualTo(STABLE_MILLIS); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenKeepReceivingScreenChanges() + throws Exception { + startPeriodicEventGenerator(buildScreenStateChangeGenerator()); + listener.await(); + } + + private Runnable buildScreenStateChangeGenerator() { + return () -> ((UnlockListener) listener) + .screenChanged(""); + } +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/VisibleCursorListenerIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/VisibleCursorListenerIT.java new file mode 100644 index 00000000..3451ebcf --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/VisibleCursorListenerIT.java @@ -0,0 +1,86 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import static org.mockito.Mockito.when; + +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.wait.CursorWaitCondition; +import java.util.Optional; +import java.util.concurrent.TimeoutException; +import nl.lxtreme.jvt220.terminal.ScreenChangeListener; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class VisibleCursorListenerIT extends Vt420ConditionWaiterIT { + + private static final Position EXPECTED_CURSOR_POSITION = new Position(12, 42); + private static final Position DEFAULT_CURSOR_POSITION = new Position(1, 1); + + @Override + protected Vt420ConditionWaiter buildConditionWaiter() { + return new VisibleCursorListener( + new CursorWaitCondition(EXPECTED_CURSOR_POSITION, TIMEOUT_MILLIS, STABLE_MILLIS), + client, + stableTimeoutExecutor, + exceptionHandler); + } + + @Override + public void setup() throws Exception { + when(client.getCursorPosition()).thenReturn(Optional.of(DEFAULT_CURSOR_POSITION)); + super.setup(); + } + + @Test + public void shouldUnblockWhenCursorAlreadyInExpectedPosition() throws Exception { + setupExpectedCursorPosition(); + super.setup(); + listener.await(); + } + + private void setupExpectedCursorPosition() { + when(client.getCursorPosition()).thenReturn(Optional.of(EXPECTED_CURSOR_POSITION)); + } + + private Runnable buildStateChangeGenerator() { + return () -> ((ScreenChangeListener) listener) + .screenChanged("event"); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenCursorIsNotInExpectedPosition() + throws Exception { + buildStateChangeGenerator().run(); + listener.await(); + } + + @Test + public void shouldUnlockWhenCursorChangePositionThroughNewEventArrived() + throws Exception { + setupExpectedCursorPosition(); + startSingleEventGenerator(Long.divideUnsigned(STABLE_MILLIS, 4), buildStateChangeGenerator()); + listener.await(); + } + + @Test(expected = TimeoutException.class) + public void shouldThrowTimeoutExceptionWhenEventsArriveInPeriodsLowerThanStablePeriod() + throws Exception { + setupCursorRepositioningEmulator(); + startPeriodicEventGenerator(buildStateChangeGenerator()); + listener.await(); + } + + private void setupCursorRepositioningEmulator() { + when(client.getCursorPosition()).thenAnswer(new Answer() { + private boolean mockToExpectedPos = true; + + @Override + public Position answer(InvocationOnMock invocation) { + mockToExpectedPos = !mockToExpectedPos; + return mockToExpectedPos + ? EXPECTED_CURSOR_POSITION : DEFAULT_CURSOR_POSITION; + } + }); + } + +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420ConditionWaiterIT.java b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420ConditionWaiterIT.java new file mode 100644 index 00000000..efba050d --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/rte/protocols/vt420/listeners/Vt420ConditionWaiterIT.java @@ -0,0 +1,16 @@ +package com.blazemeter.jmeter.rte.protocols.vt420.listeners; + +import com.blazemeter.jmeter.rte.core.listeners.ConditionWaiterIT; +import com.blazemeter.jmeter.rte.protocols.vt420.Vt420Client; +import com.blazemeter.jmeter.rte.protocols.vt420.listeners.Vt420ConditionWaiter; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) + +public abstract class Vt420ConditionWaiterIT extends ConditionWaiterIT> { + + @Mock + protected Vt420Client client; +} diff --git a/src/test/java/com/blazemeter/jmeter/rte/recorder/RTERecorderTest.java b/src/test/java/com/blazemeter/jmeter/rte/recorder/RTERecorderTest.java index f9c8d2fc..96262302 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/recorder/RTERecorderTest.java +++ b/src/test/java/com/blazemeter/jmeter/rte/recorder/RTERecorderTest.java @@ -72,15 +72,20 @@ public class RTERecorderTest { private static final String SELECTED_TEXT = "selected text"; private static final Screen TEST_SCREEN = Screen.valueOf("test\n"); private static final String SERVER = "localhost"; + private static final RteIOException RTE_IO_EXCEPTION = new RteIOException(null, SERVER); private static final int PORT = 80; - private static final Protocol PROTOCOL = Protocol.TN5250; - private static final TerminalType TERMINAL_TYPE = PROTOCOL.createProtocolClient() + private static final Protocol TN5250_PROTOCOL = Protocol.TN5250; + private static final Protocol VT420_PROTOCOL = Protocol.VT420; + private static final TerminalType TN5250_TERMINAL_TYPE = TN5250_PROTOCOL.createProtocolClient() + .getDefaultTerminalType(); + private static final TerminalType VT420_TERMINAL_TYPE = VT420_PROTOCOL.createProtocolClient() .getDefaultTerminalType(); private static final SSLType SSL_TYPE = SSLType.NONE; private static final long TIMEOUT = 10000; private static final Position CURSOR_POSITION = new Position(2, 1); private static final List INPUTS = Collections .singletonList(new CoordInput(CURSOR_POSITION, "testusr")); + @Rule public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); private TestElement testStateListener; @@ -117,6 +122,7 @@ public void setup() throws IllegalUserActionException { rteRecorder = new RTERecorder(terminalEmulatorSupplier, finder, p -> terminalClient, treeModel); setupRecorder(); + rteRecorder.setInputProvider(input -> input); } public void setupTreeModel() { @@ -152,7 +158,7 @@ public void setupRecorder() { rteRecorder.setConnectionTimeout(Long.toString(TIMEOUT)); rteRecorder.setServer(SERVER); rteRecorder.setSSLType(SSL_TYPE); - rteRecorder.setTerminalType(TERMINAL_TYPE); + rteRecorder.setTerminalType(TN5250_TERMINAL_TYPE); } @Test @@ -171,18 +177,19 @@ public void shouldAddRteConfigToTargetControllerNodeWhenStart() throws Exception connect(); ArgumentCaptor argument = ArgumentCaptor.forClass(TestElement.class); verify(treeModel).addComponent(argument.capture(), eq(treeNode)); - assertThat(argument.getValue()).isEqualTo(buildExpectedConfig()); + assertThat(argument.getValue()) + .isEqualTo(buildExpectedConfig(TN5250_PROTOCOL, TN5250_TERMINAL_TYPE.getId())); } - private ConfigTestElement buildExpectedConfig() { + private ConfigTestElement buildExpectedConfig(Protocol protocol, String terminalTypeID) { ConfigTestElement expectedConfig = new ConfigTestElement(); expectedConfig.setName("bzm-RTE-config"); expectedConfig.setProperty(TestElement.GUI_CLASS, RTEConfigGui.class.getName()); expectedConfig.setProperty(RTESampler.CONFIG_SERVER, SERVER); expectedConfig.setProperty(RTESampler.CONFIG_PORT, Integer.toString(PORT)); - expectedConfig.setProperty(RTESampler.CONFIG_PROTOCOL, PROTOCOL.name()); + expectedConfig.setProperty(RTESampler.CONFIG_PROTOCOL, protocol.name()); expectedConfig.setProperty(RTESampler.CONFIG_SSL_TYPE, SSL_TYPE.name()); - expectedConfig.setProperty(RTESampler.CONFIG_TERMINAL_TYPE, TERMINAL_TYPE.getId()); + expectedConfig.setProperty(RTESampler.CONFIG_TERMINAL_TYPE, terminalTypeID); expectedConfig.setProperty(RTESampler.CONFIG_CONNECTION_TIMEOUT, Long.toString(TIMEOUT)); return expectedConfig; } @@ -208,7 +215,7 @@ public void shouldStartTerminalEmulatorWhenStart() throws Exception { @Test public void shouldConnectTerminalClientWhenStart() throws Exception { connect(); - verify(terminalClient).connect(SERVER, PORT, SSL_TYPE, TERMINAL_TYPE, TIMEOUT); + verify(terminalClient).connect(SERVER, PORT, SSL_TYPE, TN5250_TERMINAL_TYPE, TIMEOUT); } @Test @@ -218,7 +225,8 @@ public void shouldSetEmulatorScreenWhenTerminalStateChange() throws Exception { } @Test - public void shouldSetEmulatorKeyboardLockWhenTerminalStateChange() throws Exception { + public void shouldSetEmulatorKeyboardLockOrUnlockWhenTerminalStateChange() throws Exception { + when(terminalClient.isInputInhibited()).thenReturn(Optional.of(false)); connect(); verify(terminalEmulator).setKeyboardLock(false); } @@ -238,7 +246,7 @@ public void shouldSetEmulatorCursorWhenTerminalStateChange() throws Exception { @Test public void shouldNotifyErrorResultToChildrenWhenStartWithFailingTerminalClientConnect() throws Exception { - RteIOException connectionException = new RteIOException(null, "localhost"); + RteIOException connectionException = new RteIOException(null, SERVER); doThrow(connectionException).when(terminalClient) .connect(anyString(), anyInt(), any(), any(), anyLong()); connect(); @@ -260,7 +268,7 @@ private RteSampleResultBuilder buildBasicSampleResultBuilder(Action action) { .withLabel("bzm-RTE-" + action.name()) .withProtocol(Protocol.TN5250) .withTerminalType(new TerminalType("IBM-3179-2", new Dimension(80, 24))) - .withServer("localhost") + .withServer(SERVER) .withPort(80) .withSslType(SSLType.NONE); } @@ -279,6 +287,7 @@ public void shouldSetEmulatorCursorWhenStart() throws Exception { @Test public void shouldSetEmulatorKeyboardLockWhenStart() throws Exception { + when(terminalClient.isInputInhibited()).thenReturn(Optional.of(false)); connect(); verify(terminalEmulator).setKeyboardLock(false); } @@ -293,7 +302,7 @@ public void shouldSoundEmulatorAlarmWhenStart() throws Exception { public void shouldAddConnectionSamplerWhenStartWithFailingTerminalClientConnect() throws Exception { doThrow(RteIOException.class).when(terminalClient).connect(SERVER, PORT, SSL_TYPE, - TERMINAL_TYPE, TIMEOUT); + TN5250_TERMINAL_TYPE, TIMEOUT); connect(); ArgumentCaptor argument = ArgumentCaptor.forClass(TestElement.class); @@ -334,7 +343,7 @@ public void shouldAddARequestListenerAsTerminalStateListenerWhenStart() throws E public void shouldDisconnectTerminalClientWhenStartWithFailingTerminalClientConnect() throws Exception { doThrow(RteIOException.class).when(terminalClient).connect(SERVER, PORT, SSL_TYPE, - TERMINAL_TYPE, TIMEOUT); + TN5250_TERMINAL_TYPE, TIMEOUT); connect(); verify(terminalClient).disconnect(); } @@ -405,6 +414,7 @@ public void shouldNotifyRecordingListenerWhenCloseTerminal() throws Exception { @Test public void shouldLockEmulatorKeyboardWhenAttentionKey() throws Exception { + when(terminalClient.isInputInhibited()).thenReturn(Optional.of(true)); connect(); rteRecorder.onAttentionKey(AttentionKey.ENTER, INPUTS, ""); /* @@ -490,7 +500,8 @@ public void shouldAddPendingSamplerAndDisconnectSamplerToTestPlanWhenStop() thro */ verify(treeModel, times(3)) .addComponent(argument.capture(), eq(treeNode)); - assertThat(Arrays.asList(buildExpectedConfig(), buildExpectedConnectionSampler(), + assertThat(Arrays.asList(buildExpectedConfig(TN5250_PROTOCOL, TN5250_TERMINAL_TYPE.getId()), + buildExpectedConnectionSampler(), buildExpectedDisconnectionSampler())).isEqualTo(argument.getAllValues()); } @@ -518,7 +529,8 @@ public void shouldRegisterRequestListenerWhenAttentionKey() throws Exception { public void shouldSendInputsAndAttentionKeyToTerminalClientWhenAttentionKey() throws Exception { connect(); rteRecorder.onAttentionKey(AttentionKey.ENTER, INPUTS, ""); - verify(terminalClient).send(INPUTS, AttentionKey.ENTER); + verify(terminalClient) + .send(INPUTS, AttentionKey.ENTER, RTESampler.DEFAULT_CONNECTION_TIMEOUT_MILLIS); } @Test @@ -544,29 +556,23 @@ public void shouldNotifyRecordingStateListenerWhenUnsupportedOperationOnAttentio } private void setupExceptionOnAttentionKey(Exception exception) throws RteIOException { - doThrow(exception).when(terminalClient).send(any(), any()); + doThrow(exception).when(terminalClient).send(any(), any(), anyLong()); } @Test public void shouldNotifyRecordingStateListenerOfExceptionWhenOnAttentionKey() throws RteIOException, TimeoutException, InterruptedException { - setupExceptionOnTerminalClientSend(); - RteIOException exception = new RteIOException(null, SERVER); - doThrow(exception).when(terminalClient).send(any(), any()); + setupExceptionOnAttentionKey(RTE_IO_EXCEPTION); rteRecorder.setRecordingStateListener(recorderListener); connect(); rteRecorder.onAttentionKey(AttentionKey.ENTER, INPUTS, ""); - verify(recorderListener, timeout(TIMEOUT)).onRecordingException(exception); - } - - private void setupExceptionOnTerminalClientSend() throws RteIOException { - doThrow(new RteIOException(null, SERVER)).when(terminalClient).send(any(), any()); + verify(recorderListener, timeout(TIMEOUT)).onRecordingException(RTE_IO_EXCEPTION); } @Test public void shouldStopEmulatorWhenExceptionOnAttentionKey() throws RteIOException, TimeoutException, InterruptedException { - setupExceptionOnTerminalClientSend(); + setupExceptionOnAttentionKey(RTE_IO_EXCEPTION); connect(); rteRecorder.onAttentionKey(AttentionKey.ENTER, INPUTS, ""); verify(terminalEmulator).stop(); @@ -575,7 +581,7 @@ public void shouldStopEmulatorWhenExceptionOnAttentionKey() @Test public void shouldDisconnectWhenExceptionOnAttentionKey() throws RteIOException, TimeoutException, InterruptedException { - setupExceptionOnTerminalClientSend(); + setupExceptionOnAttentionKey(RTE_IO_EXCEPTION); connect(); rteRecorder.onAttentionKey(AttentionKey.ENTER, INPUTS, ""); verify(terminalClient).disconnect(); @@ -656,5 +662,49 @@ public void shouldAddSamplerWithDefaultSampleNameWhenConnectAndDisconnect() softly.assertThat(argument.getAllValues().get(2).getName()).isEqualTo("bzm-RTE-DISCONNECT"); softly.assertThat(argument.getAllValues().get(1).getName()).isEqualTo("bzm-RTE-CONNECT"); } + + @Test + public void shouldAddRteConfigToTargetControllerNodeWhenStartUsingVt420() throws Exception { + connectToVT420Terminal(); + VT420_PROTOCOL.createProtocolClient().getDefaultTerminalType(); + ArgumentCaptor argument = ArgumentCaptor.forClass(TestElement.class); + verify(treeModel).addComponent(argument.capture(), eq(treeNode)); + assertThat(argument.getValue()) + .isEqualTo(buildExpectedConfig(VT420_PROTOCOL, VT420_TERMINAL_TYPE.getId())); + } + + private void connectToVT420Terminal() throws TimeoutException, InterruptedException { + rteRecorder.setProtocol(VT420_PROTOCOL); + rteRecorder.setTerminalType(VT420_TERMINAL_TYPE); + connect(); + } + + @Test + public void shouldAddConnectSamplerToTargetControllerNodeWhenStartUsingVt420() throws Exception { + connectToVT420Terminal(); + rteRecorder.onAttentionKey(AttentionKey.ENTER, Collections.EMPTY_LIST, "Input"); + VT420_PROTOCOL.createProtocolClient().getDefaultTerminalType(); + ArgumentCaptor argument = ArgumentCaptor.forClass(TestElement.class); + //Method called thrice due to addition of: Config and Connect samplers. + verify(treeModel, times(2)).addComponent(argument.capture(), eq(treeNode)); + + assertThat(argument.getValue()) + .isEqualTo(buildExpectedConnectionSampler()); + } + + @Test + public void shouldAddDisconnectSamplerToTargetControllerNodeWhenStopUsingVt420() + throws Exception { + connectToVT420Terminal(); + rteRecorder.onRecordingStop(); + VT420_PROTOCOL.createProtocolClient().getDefaultTerminalType(); + ArgumentCaptor argument = ArgumentCaptor.forClass(TestElement.class); + //Method called thrice due to addition of: Config, Connect and Disconnect samplers. + verify(treeModel, times(3)).addComponent(argument.capture(), eq(treeNode)); + + assertThat(argument.getValue()) + .isEqualTo(buildExpectedDisconnectionSampler()); + } + } diff --git a/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/AlarmLabelIT.java b/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/IntermittentLabelIT.java similarity index 79% rename from src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/AlarmLabelIT.java rename to src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/IntermittentLabelIT.java index ae187b59..2c767834 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/AlarmLabelIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/IntermittentLabelIT.java @@ -5,13 +5,8 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import org.assertj.core.api.JUnitSoftAssertions; import org.assertj.swing.fixture.FrameFixture; import org.junit.After; @@ -22,15 +17,21 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -@RunWith(MockitoJUnitRunner.class) -public class AlarmLabelIT { +/* + Mainly this class tests the IntermittentLabel when used by alarm. + When IntermittentLabel is used as blocked cursor for CharacterEmulator is tested in + Xtn5250TerminalEmulator. +*/ - private FrameFixture frame; - private AlarmLabel alarmLabel; +@RunWith(MockitoJUnitRunner.class) +public class IntermittentLabelIT { @Rule public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); + + private FrameFixture frame; + private IntermittentLabel alarmLabel; @Mock private ScheduledFuture future; @@ -39,7 +40,9 @@ public class AlarmLabelIT { @Before public void setup() { executorService = new ScheduledExecutorServiceTest(future); - alarmLabel = new AlarmLabel(executorService); + alarmLabel = new IntermittentLabel("alarm.png", executorService); + alarmLabel.setDefaultTask(() -> alarmLabel.setVisible(false)); + alarmLabel.setOnBlinkTask(() -> alarmLabel.setVisible(!alarmLabel.isVisible())); frame = showInFrame(alarmLabel); } @@ -54,7 +57,7 @@ public void shouldBlinkForAPeriodWhenSoundAlarm() { .asList(false, true, false, true, false, true, false, true, false, true, false, false); List result = new ArrayList<>(); result.add(alarmLabel.isVisible()); - alarmLabel.soundAlarm(); + alarmLabel.blink(); for (int i = 0; i < 10; i++) { executorService.tick(); result.add(alarmLabel.isVisible()); @@ -71,12 +74,12 @@ public void shouldStartNewBlinkPeriodWhenAlarmIsSoundedAndSoundAlarm() { false, false); List result = new ArrayList<>(); result.add(alarmLabel.isVisible()); - alarmLabel.soundAlarm(); + alarmLabel.blink(); for (int i = 0; i < 2; i++) { executorService.tick(); result.add(alarmLabel.isVisible()); } - alarmLabel.soundAlarm(); + alarmLabel.blink(); for (int i = 0; i < 10; i++) { executorService.tick(); result.add(alarmLabel.isVisible()); @@ -86,6 +89,5 @@ public void shouldStartNewBlinkPeriodWhenAlarmIsSoundedAndSoundAlarm() { assertThat(result).isEqualTo(expected); } - } diff --git a/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanelIT.java b/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanelIT.java index e2651bb1..6e94feeb 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanelIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/StatusPanelIT.java @@ -19,6 +19,10 @@ public class StatusPanelIT { + public static final ImageIcon KEYBOARD_UNLOCKED_ICON = ThemedIcon + .fromResourceName("keyboard-unlocked.png"); + public static final ImageIcon CURSOR_ICON = ThemedIcon.fromResourceName( + "cursor.png"); private static final long HELP_FRAME_VISIBLE_TIMEOUT_MILLIS = 1000; private static final String POSITION_LABEL = "positionLabel"; private static final String KEYBOARD_LABEL = "keyboardLabel"; @@ -27,9 +31,6 @@ public class StatusPanelIT { private static final String EXPECTED_POSITION_TEXT = "row: 66 / column: 66"; private static final ImageIcon KEYBOARD_LOCKED_ICON = ThemedIcon .fromResourceName("keyboard-locked.png"); - private static final ImageIcon KEYBOARD_UNLOCKED_ICON = ThemedIcon - .fromResourceName("keyboard-unlocked.png"); - @Rule public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); diff --git a/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulatorIT.java b/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulatorIT.java index b87b3225..6d0b023e 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulatorIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/recorder/emulator/Xtn5250TerminalEmulatorIT.java @@ -2,14 +2,26 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.swing.timing.Pause.pause; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.blazemeter.jmeter.rte.core.AttentionKey; +import com.blazemeter.jmeter.rte.core.CharacterBasedProtocolClient; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; +import com.blazemeter.jmeter.rte.core.Position; +import com.blazemeter.jmeter.rte.core.RteProtocolClient; import com.blazemeter.jmeter.rte.core.Screen; +import com.blazemeter.jmeter.rte.sampler.NavigationType; +import com.blazemeter.jmeter.rte.sampler.gui.ThemedIcon; import com.google.common.base.Charsets; import com.google.common.io.Resources; import java.awt.Component; @@ -21,19 +33,30 @@ import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import javax.swing.Icon; import javax.swing.ImageIcon; -import javax.swing.JLabel; +import javax.swing.JButton; import org.apache.commons.lang3.StringUtils; import org.assertj.core.api.JUnitSoftAssertions; import org.assertj.swing.core.KeyPressInfo; import org.assertj.swing.driver.JComponentDriver; +import org.assertj.swing.exception.ComponentLookupException; import org.assertj.swing.finder.JOptionPaneFinder; import org.assertj.swing.fixture.FrameFixture; import org.assertj.swing.fixture.JButtonFixture; @@ -46,8 +69,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.internal.stubbing.answers.AnswersWithDelay; import org.mockito.junit.MockitoJUnitRunner; + @RunWith(MockitoJUnitRunner.class) public class Xtn5250TerminalEmulatorIT { @@ -56,6 +81,12 @@ public class Xtn5250TerminalEmulatorIT { public static final String ASSERTION_TEST_LITERAL = "Assertion Test"; public static final String CONNECTING_LITERAL = "CONNECTING"; public static final String DEFAULT_SAMPLE_NAME_INPUT_VALUE = "DEFAULT_INPUT_VALUE"; + public static final String BLOCK_CURSOR_LABEL = "blockedCursor"; + public static final String BLOCKED_CURSOR_RESOURCE_NAME = "blocked-cursor.png"; + public static final String KEYBOARD_UNLOCKED_RESOURCE_NAME = "keyboard-unlocked.png"; + public static final String CHUNK_OF_SCREEN = "*****"; + public static final String DEVELOPER_ID = "TT"; + public static final String WORKDATE_LITERAL_VALUE = "00"; private static final long PAUSE_TIMEOUT = 10000; private static final int COLUMNS = 80; private static final int ROWS = 24; @@ -63,13 +94,25 @@ public class Xtn5250TerminalEmulatorIT { private static final String PASTE_BUTTON = "pasteButton"; private static final String INPUT_BY_LABEL_BUTTON = "labelButton"; private static final String TEST_SCREEN_FILE = "test-screen.txt"; - private static final String TEST_SCREEN_PRESS_KEY_ON_FIELD_FILE = "test-screen-press-key-on-field.txt"; + private static final String TEST_SCREEN_PRESS_KEY_ON_FIELD_FILE = "test-screen-press-key-on" + + "-field.txt"; private static final String WAIT_FOR_TEXT_BUTTON = "waitForTextButton"; private static final String ASSERTION_BUTTON = "assertionButton"; + private static final String KEYBOARD_LABEL = "keyboardLabel"; + private static final Position FIRST_VT_POS = new Position(11, 41); + private static final Position SECOND_VT_POS = new Position(11, 42); + @Rule public final JUnitSoftAssertions softly = new JUnitSoftAssertions(); private Xtn5250TerminalEmulator xtn5250TerminalEmulator; private FrameFixture frame; + private CharacterBasedEmulator characterBasedEmulator; + private Screen screen; + @Mock + private RteProtocolClient protocolClient; + + @Mock + private CharacterBasedProtocolClient characterBasedProtocolClient; @Mock private TerminalEmulatorListener listener; @@ -144,9 +187,9 @@ private Set buildSupportedAttentionKeys() { @Before public void setup() { - xtn5250TerminalEmulator = new Xtn5250TerminalEmulator(); + xtn5250TerminalEmulator = new Xtn5250TerminalEmulator(new FieldBasedEmulator()); xtn5250TerminalEmulator.setSupportedAttentionKeys(buildSupportedAttentionKeys()); - + xtn5250TerminalEmulator.setProtocolClient(protocolClient); } @After @@ -197,7 +240,7 @@ public void shouldShowTheScreenExpectedWhenSetScreen() throws IOException { public void shouldGetProperTextWhenPressKeyOnField() throws IOException { setScreen(""); xtn5250TerminalEmulator.setKeyboardLock(false); - sendKey(KeyEvent.VK_E, 0, 2, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_E, 0, 2, 1); awaitTextInScreen(getFileContent(TEST_SCREEN_PRESS_KEY_ON_FIELD_FILE)); } @@ -205,7 +248,7 @@ public void shouldGetProperTextWhenPressKeyOnField() throws IOException { public void shouldGetProperTextWhenPressKeyOnFieldAndKeyboardIsLocked() throws IOException { setScreen(""); xtn5250TerminalEmulator.setKeyboardLock(true); - sendKey(KeyEvent.VK_E, 0, 2, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_E, 0, 2, 1); awaitTextInScreen(getFileContent(TEST_SCREEN_FILE)); } @@ -213,7 +256,7 @@ public void shouldGetProperTextWhenPressKeyOnFieldAndKeyboardIsLocked() throws I public void shouldDeleteTextWhenPressBackspaceKey() throws IOException { setScreen("E"); xtn5250TerminalEmulator.setKeyboardLock(false); - sendKey(KeyEvent.VK_BACK_SPACE, 0, 2, 2); + sendKeyWithCursorUpdate(KeyEvent.VK_BACK_SPACE, 0, 2, 2); awaitTextInScreen(getFileContent(TEST_SCREEN_FILE)); } @@ -221,7 +264,7 @@ public void shouldDeleteTextWhenPressBackspaceKey() throws IOException { public void shouldGetProperTextWhenPressKeyOutOfField() throws IOException { setScreen(""); xtn5250TerminalEmulator.setKeyboardLock(false); - sendKey(KeyEvent.VK_E, 0, 1, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_E, 0, 1, 1); awaitTextInScreen(getFileContent(TEST_SCREEN_FILE)); } @@ -236,10 +279,10 @@ private void setScreen(String text, String sampleName) { frame.show(); } - private void sendKey(int key, int modifiers, int row, int column) { + private void sendKeyWithCursorUpdate(int key, int modifiers, int row, int column) { Component focusedComponent = frame.robot().finder().find(Component::isFocusOwner); JComponentDriver driver = new JComponentDriver(frame.robot()); - xtn5250TerminalEmulator.setCursor(row, column); + updateCursorPos(row, column); driver.pressAndReleaseKey(focusedComponent, KeyPressInfo.keyCode(key).modifiers(modifiers)); } @@ -256,7 +299,7 @@ public boolean test() { public void shouldCallTheListenerWhenPressAnyAttentionKey() { setScreen(""); xtn5250TerminalEmulator.addTerminalEmulatorListener(listener); - sendKey(KeyEvent.VK_F1, 0, 2, 2); + sendKeyWithCursorUpdate(KeyEvent.VK_F1, 0, 2, 2); verify(listener, timeout(PAUSE_TIMEOUT)).onAttentionKey(AttentionKey.F1, new ArrayList<>(), ""); } @@ -264,7 +307,7 @@ public void shouldCallTheListenerWhenPressAnyAttentionKey() { public void shouldCallTheListenerWhenPressControlAttentionKey() { setScreen(""); xtn5250TerminalEmulator.addTerminalEmulatorListener(listener); - sendKey(KeyEvent.VK_CONTROL, KeyEvent.CTRL_MASK, 2, 2); + sendKeyWithCursorUpdate(KeyEvent.VK_CONTROL, KeyEvent.CTRL_MASK, 2, 2); verify(listener, timeout(PAUSE_TIMEOUT)) .onAttentionKey(AttentionKey.RESET, new ArrayList<>(), ""); } @@ -274,7 +317,17 @@ public void shouldCopyTextWhenClickCopyButton() throws IOException, UnsupportedF setScreen(""); xtn5250TerminalEmulator.setSelectedArea(new Rectangle(0, 0, 5, 1)); clickButton(COPY_BUTTON); - assertTextIsInClipboard("*****"); + assertTextIsInClipboard(CHUNK_OF_SCREEN); + } + + @Test + public void shouldCopyTextWhenClickCopyButtonUsingCharacterBasedEmulator() throws IOException, + UnsupportedFlavorException { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setSelectedArea(new Rectangle(30, 0, 18, 1)); + clickButton(COPY_BUTTON); + assertTextIsInClipboard("**W E L C O M E **"); } @Test @@ -282,8 +335,8 @@ public void shouldCopyTextWhenPressShortcut() throws IOException, UnsupportedFlavorException { setScreen(""); xtn5250TerminalEmulator.setSelectedArea(new Rectangle(0, 0, 5, 1)); - sendKey(KeyEvent.VK_C, getMenuShortcutKeyMask(), 0, 0); - assertTextIsInClipboard("*****"); + sendKeyWithCursorUpdate(KeyEvent.VK_C, getMenuShortcutKeyMask(), 0, 0); + assertTextIsInClipboard(CHUNK_OF_SCREEN); } private void clickButton(String name) { @@ -302,7 +355,7 @@ private void assertTextIsInClipboard(String text) throws IOException, Unsupporte @Test public void shouldPasteTextWhenClickPasteButton() throws IOException { setScreen(""); - xtn5250TerminalEmulator.setCursor(2, 1); + updateCursorPos(2, 1); addTextToClipboard("e"); clickButton(PASTE_BUTTON); awaitTextInScreen(getFileContent(TEST_SCREEN_PRESS_KEY_ON_FIELD_FILE)); @@ -311,9 +364,9 @@ public void shouldPasteTextWhenClickPasteButton() throws IOException { @Test public void shouldPasteTextWhenPressShortcut() throws IOException { setScreen(""); - xtn5250TerminalEmulator.setCursor(2, 1); + updateCursorPos(2, 1); addTextToClipboard("e"); - sendKey(KeyEvent.VK_V, getMenuShortcutKeyMask(), 2, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_V, getMenuShortcutKeyMask(), 2, 1); awaitTextInScreen(getFileContent(TEST_SCREEN_PRESS_KEY_ON_FIELD_FILE)); } @@ -335,7 +388,7 @@ private String getFileContent(String file) throws IOException { @Test public void shouldShowUserMessageWhenUnsupportedAttentionKey() { setScreen(""); - sendKey(KeyEvent.VK_ESCAPE, 0, 0, 0); + sendKeyWithCursorUpdate(KeyEvent.VK_ESCAPE, 0, 0, 0); findOptionPane().requireMessage("ATTN not supported for current protocol"); } @@ -360,10 +413,10 @@ public void shouldSendInputByLabelThroughListenerWhenInputByLabel() { clickButton(INPUT_BY_LABEL_BUTTON); String test = "t"; String input = test + StringUtils.repeat(' ', COLUMNS - test.length()); - sendKey(KeyEvent.VK_T, 0, 2, 1); - sendKey(KeyEvent.VK_ENTER, 0, 2, 5); + sendKeyWithCursorUpdate(KeyEvent.VK_T, 0, 2, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_ENTER, 0, 2, 5); List inputs = new ArrayList<>(); - inputs.add(new LabelInput("*****", input)); + inputs.add(new LabelInput(CHUNK_OF_SCREEN, input)); verify(listener).onAttentionKey(AttentionKey.ENTER, inputs, ""); } @@ -373,11 +426,11 @@ public void shouldNotifyListenerOfMultipleInputByLabel() { xtn5250TerminalEmulator.addTerminalEmulatorListener(listener); xtn5250TerminalEmulator.setSelectedArea(new Rectangle(0, 0, 11, 1)); clickButton(INPUT_BY_LABEL_BUTTON); - sendKey(KeyEvent.VK_T, 0, 1, 14); + sendKeyWithCursorUpdate(KeyEvent.VK_T, 0, 1, 14); xtn5250TerminalEmulator.setSelectedArea(new Rectangle(0, 1, 15, 1)); clickButton(INPUT_BY_LABEL_BUTTON); - sendKey(KeyEvent.VK_Y, 0, 2, 18); - sendKey(KeyEvent.VK_ENTER, 0, 2, 19); + sendKeyWithCursorUpdate(KeyEvent.VK_Y, 0, 2, 18); + sendKeyWithCursorUpdate(KeyEvent.VK_ENTER, 0, 2, 19); verify(listener) .onAttentionKey(AttentionKey.ENTER, buildExpectedInputListForMultipleInputsByLabel(), ""); @@ -428,7 +481,7 @@ public void shouldCallTheListenerWhenPressWaitForTextButton() { xtn5250TerminalEmulator.setSelectedArea(new Rectangle(1, 0, 5, 4)); clickButton(WAIT_FOR_TEXT_BUTTON); verify(listener, timeout(PAUSE_TIMEOUT)) - .onWaitForText("*****\n " + " \n" + "EXTO \n" + "EXTO "); + .onWaitForText(CHUNK_OF_SCREEN + "\n " + " \n" + "EXTO \n" + "EXTO "); } @Test @@ -468,7 +521,7 @@ public void shouldNotifyListenerWhenInputInhibitedOnSampleName() { xtn5250TerminalEmulator.addTerminalEmulatorListener(listener); xtn5250TerminalEmulator.setKeyboardLock(false); setSampleName(CONNECTING_LITERAL); - sendKey(KeyEvent.VK_ENTER, 0, 2, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_ENTER, 0, 2, 1); verify(listener).onAttentionKey(AttentionKey.ENTER, new ArrayList<>(), CONNECTING_LITERAL); } @@ -485,7 +538,7 @@ public void shouldSetDefaultValueInFieldWhenOnAttentionKey() { setScreen("", DEFAULT_SAMPLE_NAME_INPUT_VALUE); xtn5250TerminalEmulator.addTerminalEmulatorListener(listener); xtn5250TerminalEmulator.setKeyboardLock(false); - sendKey(KeyEvent.VK_ENTER, 0, 2, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_ENTER, 0, 2, 1); assertThat(frame.textBox(SAMPLE_NAME_FIELD).text()).isEqualTo(DEFAULT_SAMPLE_NAME_INPUT_VALUE); } @@ -495,6 +548,465 @@ public void shouldSwitchCredentialVisibilityIconWhenClickIcon() { frame.label("showCredentials").click(); Icon actual = frame.label("showCredentials").target().getIcon(); ImageIcon expected = new ImageIcon("/light-theme/visible-credentials.png"); - assertThat(((ImageIcon)actual).getImage().equals(expected.getImage())); + assertThat(((ImageIcon) actual).getImage().equals(expected.getImage())); + } + + @Test + public void shouldProperBuildInputsWithTabsWhenSendCredentialsUsingVT() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + updateCharacterBasedWelcomeScreen("T "); + setCurrentCursorPositionAndScreen(FIRST_VT_POS); + when(characterBasedProtocolClient.getCursorPosition()) + .thenReturn(Optional.of(SECOND_VT_POS)); + sendKeyInCurrentPosition(KeyEvent.VK_T, 11, 42); + + updateCharacterBasedWelcomeScreen(DEVELOPER_ID); + setCurrentCursorPositionAndScreen(new Position(11, 43)); + sendKeyInCurrentPosition(KeyEvent.VK_T, 11, 43); + + setCurrentCursorPositionAndScreen(new Position(13, 41)); + sendKeyInCurrentPosition(KeyEvent.VK_TAB, 13, 41); + + updateCharacterBasedWelcomeScreen(DEVELOPER_ID, "0 "); + setCurrentCursorPositionAndScreen(new Position(13, 42)); + sendKeyInCurrentPosition(KeyEvent.VK_0, 13, 42); + + updateCharacterBasedWelcomeScreen(DEVELOPER_ID, WORKDATE_LITERAL_VALUE); + setCurrentCursorPositionAndScreen(new Position(13, 43)); + sendKeyInCurrentPosition(KeyEvent.VK_0, 13, 43); + + assertThat(xtn5250TerminalEmulator.getInputs()) + .isEqualTo(buildExpectedTabulatorInput()); + + } + + private List buildExpectedTabulatorInput() { + return Arrays + .asList(new NavigationInput(0, NavigationType.TAB, "tt"), new NavigationInput(1, + NavigationType.TAB, WORKDATE_LITERAL_VALUE)); + } + + public void setCurrentCursorPositionAndScreen(Position cursor) { + when(characterBasedProtocolClient.getScreen()).thenReturn(screen); + when(characterBasedProtocolClient.getCursorPosition()) + .thenReturn(Optional.of(cursor)); + } + + private void updateCursorPos(int i, int i2) { + xtn5250TerminalEmulator.setCursor(i, i2); + } + + private void sendKeyInCurrentPosition(int key, int r, int c) { + Component focusedComponent = frame.robot().finder().find(Component::isFocusOwner); + JComponentDriver driver = new JComponentDriver(frame.robot()); + driver.pressAndReleaseKey(focusedComponent, KeyPressInfo.keyCode(key).modifiers(0)); + xtn5250TerminalEmulator.setCursor(r, c); + xtn5250TerminalEmulator.setScreen(screen, ""); + characterBasedEmulator.screenChanged(screen.getText()); + } + + private void setupInteractiveCharacterEmulator() { + setupCharacterEmulator(true); + } + + private void setupCharacterEmulator(boolean interactive) { + characterBasedEmulator = new CharacterBasedEmulator(); + xtn5250TerminalEmulator = new Xtn5250TerminalEmulator(characterBasedEmulator); + xtn5250TerminalEmulator.setSupportedAttentionKeys(buildSupportedAttentionKeys()); + xtn5250TerminalEmulator.setProtocolClient(characterBasedProtocolClient); + xtn5250TerminalEmulator.addTerminalEmulatorListener(listener); + xtn5250TerminalEmulator.setScreenSize(COLUMNS, ROWS); + xtn5250TerminalEmulator.setScreen(screen, ""); + characterBasedEmulator.setKeyboardStatus(true); + characterBasedEmulator.screenChanged(screen.getText()); + if (interactive) { + setScreenChangeEventWhenSendingInputs(); + } + frame = new FrameFixture(xtn5250TerminalEmulator); + frame.show(); + } + + private void setScreenChangeEventWhenSendingInputs() { + doAnswer(invocation -> { + characterBasedEmulator.screenChanged(screen.getText()); + return null; + }).when(characterBasedProtocolClient).send(anyString()); + } + + private void updateCharacterBasedWelcomeScreen(String... inputs) { + updateCharacterBasedScreen(getWelcomeScreenText(inputs)); + + } + + private void updateCharacterBasedScreen(List screenText) { + screen = new Screen(new Dimension(80, 24)); + AtomicInteger linearPosition = new AtomicInteger(); + screenText.forEach(l -> screen.addSegment(linearPosition.getAndAdd(80), l)); + } + + private List getWelcomeScreenText(String... strings) { + return Arrays.asList( + " **W E L C O M E ** ", + " ", + " WARNING ", + " ", + " THIS MATERIAL IT IS JUST FOR TESTING PROPOUSES ", + " ", + " ", + " ", + " TESTER ID: 001 ", + " ", + " DEVELOPER ID:" + (strings.length > 0 ? strings[0] : " ") + + " ", + " ", + " WORKDATE:" + (strings.length > 1 ? strings[1] : " ") + + " ", + " ", + " PASSWORD: ", + " ", + " ", + " ", + " ", + " ", + " ENTER CANCEL ", + " ", + " ", + " "); + + } + + @Test + public void shouldNotCreateInputsWithInvalidCharacterWhenTypingInVT() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + setCurrentCursorPositionAndScreen(SECOND_VT_POS); + sendKeyInCurrentPosition(KeyEvent.VK_T, 11, 42); + assertThat(xtn5250TerminalEmulator.getInputs().isEmpty()); + } + + @Test + public void shouldLockAndUnlockKeyboardWhenSendInputAndScreenChanges() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + + updateCharacterBasedWelcomeScreen("T "); + setCurrentCursorPositionAndScreen(FIRST_VT_POS); + startSingleEventGenerator(buildScreenChangeListener()); + sendKeyWithCursorUpdate(KeyEvent.VK_T, 0, 11, 42); + xtn5250TerminalEmulator.setScreen(screen, ""); + awaitKeyboardToBeUnlocked(); + } + + private Runnable buildScreenChangeListener() { + return () -> characterBasedEmulator.screenChanged(""); + } + + private void startSingleEventGenerator(Runnable eventGenerator) { + ScheduledExecutorService eventGeneratorExecutor = Executors.newSingleThreadScheduledExecutor(); + eventGeneratorExecutor.schedule(eventGenerator, (long) 500, TimeUnit.MILLISECONDS); + } + + private void awaitKeyboardToBeUnlocked() { + pause(new Condition("Keyboard to be unlocked") { + @Override + public boolean test() { + return frame.label(KEYBOARD_LABEL).target().getIcon().equals(ThemedIcon + .fromResourceName(KEYBOARD_UNLOCKED_RESOURCE_NAME)); + } + }, PAUSE_TIMEOUT); + } + + @Test + public void shouldShowBlockedMouseIconWhenUsingVT() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + Icon actual = frame.label(BLOCK_CURSOR_LABEL).target().getIcon(); + assertThat(actual.equals(ThemedIcon + .fromResourceName(BLOCKED_CURSOR_RESOURCE_NAME))); + } + + @Test + public void shouldBlockedCursorStartBlinkingWhenClickingScreen() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + frame.robot().click(xtn5250TerminalEmulator); + awaitBlockedCursorToBlink(); + } + + private void awaitBlockedCursorToBlink() { + pause(new Condition("Blocked cursor icon has blinked.") { + @Override + public boolean test() { + return frame.label(BLOCK_CURSOR_LABEL).target().getIcon().equals(StatusPanelIT.CURSOR_ICON); + } + }, PAUSE_TIMEOUT); + } + + @Test(expected = ComponentLookupException.class) + public void shouldNotAppearBlockedCursorWhenUsingFiledBasedEmulator() { + setScreen(""); + //will not find this component therefore, ComponentLookupException is thrown. + frame.label(BLOCK_CURSOR_LABEL).isEnabled(); + } + + @Test + public void shouldNotSendAnyCharacterWhenKeyboardLock() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setKeyboardLock(true); + sendKeyWithCursorUpdate(KeyEvent.VK_Y, 0, 11, 42); + verify(characterBasedProtocolClient, never()).send(anyString()); + } + + @Test + public void shouldSendAttentionKeyWhenKeyboardLock() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setKeyboardLock(true); + sendKeyWithCursorUpdate(KeyEvent.VK_F1, 0, 11, 42); + verify(listener).onAttentionKey(any(AttentionKey.class), anyList(), + anyString()); + } + + @Test + public void shouldBuildExpectedInputsWhenUsingPaste() { + updateCharacterBasedWelcomeScreen(); + setupInteractiveCharacterEmulator(); + setCursorPositionMock(11, 41); + updateCharacterBasedWelcomeScreen("T "); + addTextToClipboard(DEVELOPER_ID.toLowerCase()); + + clickButton(PASTE_BUTTON); + awaitForPasteAvailability(); + updateCharacterBasedWelcomeScreen(DEVELOPER_ID); + xtn5250TerminalEmulator.setScreen(screen, ""); + setCursorPositionMock(12, 41); + updateCharacterBasedWelcomeScreen("0 "); + addTextToClipboard(WORKDATE_LITERAL_VALUE); + + clickButton(PASTE_BUTTON); + awaitForPasteAvailability(); + updateCharacterBasedWelcomeScreen(DEVELOPER_ID, WORKDATE_LITERAL_VALUE); + xtn5250TerminalEmulator.setScreen(screen, ""); + assertThat(xtn5250TerminalEmulator.getInputs()) + .isEqualTo(buildExpectedTabulatorInputsForCharacterBased()); + } + + private void setCursorPositionMock(int row, int column) { + when(characterBasedProtocolClient.getCursorPosition()).thenReturn(Optional.of(new Position(row, + column))).thenReturn(Optional.of(new Position(row, + column + 1))).thenReturn(Optional.of(new Position(row, + column + 1))).thenReturn(Optional.of(new Position(row, + column + 2))); + } + + private void awaitForPasteAvailability() { + pause(new Condition(" waiting for paste button to be unlocked") { + @Override + public boolean test() { + return frame.button(PASTE_BUTTON).isEnabled(); + } + }, PAUSE_TIMEOUT); + } + + private List buildExpectedTabulatorInputsForCharacterBased() { + return Arrays.asList(new NavigationInput(0, NavigationType.TAB, DEVELOPER_ID.toLowerCase()), + new NavigationInput(0, NavigationType.TAB, WORKDATE_LITERAL_VALUE)); + } + + @Test + public void shouldCloseWindowWhenWaitingForServerAnswer() { + updateCharacterBasedWelcomeScreen(); + setupManualCharacterEmulator(); + setLockWhenInputSent(); + AtomicBoolean isClosed = new AtomicBoolean(); + setupWindowListener(isClosed); + sendKeyWithCursorUpdate(KeyEvent.VK_0, 0, 1, 1); + sendKeyWithCursorUpdate(KeyEvent.VK_F, 0, 1, 1); + verify(characterBasedProtocolClient, times(1)).send(anyString()); + frame.close(); + assertThat(isClosed); + } + + private void setupWindowListener(AtomicBoolean isClosed) { + frame.target().addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + isClosed.set(true); + super.windowClosed(e); + } + }); + } + + private void setLockWhenInputSent() { + doAnswer(new AnswersWithDelay(PAUSE_TIMEOUT, null)) + .when(characterBasedProtocolClient).send(anyString()); + } + + private void setupManualCharacterEmulator() { + setupCharacterEmulator(false); + } + + @Test + public void shouldNotSetVisibleInputByLabelButtonWhenUsingCharacterBasedEmulator() { + updateCharacterBasedWelcomeScreen(); + setupManualCharacterEmulator(); + assertThat(Arrays.stream(frame.target().getComponents()) + .filter(c -> c instanceof JButton) + .anyMatch(c -> c.getName().equals("labelButton"))); + } + + @Test + public void shouldBuildDownNavigationInputWhenSendingDownArrow() { + updateArrowNavigationScreen(); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setCursor(1, 54); + when(characterBasedProtocolClient.getCursorPosition()) + .thenReturn(Optional.of(new Position(1, 54))) + .thenReturn(Optional.of(new Position(2, 47))) + .thenReturn(Optional.of(new Position(2, 48))); + updateArrowNavigationScreen(ArrowInput.ZERO); + sendKeyInCurrentPosition(KeyEvent.VK_DOWN, 2, 47); + sendKeyInCurrentPosition(KeyEvent.VK_0, 2, 26); + assertThat(xtn5250TerminalEmulator.getInputs()).isEqualTo(ArrowInput.ZERO.inputs); + } + + private void updateArrowNavigationScreen(ArrowInput... values) { + String[] texts = + Arrays.stream(values) + .map(ArrowInput::toString) + .collect(Collectors.toList()) + .toArray(new String[values.length]); + updateCharacterBasedScreen(buildArrowScreenText(texts)); + } + + private List buildArrowScreenText(String... text) { + return Arrays.asList( + " USER NAME: " + getIndex(text, 2) + + " PERMISSIONS REQUIRED: " + getIndex(text, 3) + " ", + " USER TYPE: " + getIndex(text, 1) + + " INTERNAL CODE: " + getIndex(text, 0) + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " "); + } + + private String getIndex(String[] strings, int i) { + return strings.length > i ? strings[i] : " "; + } + + @Test + public void shouldBuildLeftNavigationInputWhenSendingLeftArrow() { + updateArrowNavigationScreen(ArrowInput.ZERO); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setCursor(2, 48); + when(characterBasedProtocolClient.getCursorPosition()) + .thenReturn(Optional.of(new Position(2, 48))) + .thenReturn(Optional.of(new Position(2, 46))) + .thenReturn(Optional.of(new Position(2, 46))) + .thenReturn(Optional.of(new Position(2, 26))) + .thenReturn(Optional.of(new Position(2, 26))) + .thenReturn(Optional.of(new Position(2, 27))); + sendKeyInCurrentPosition(KeyEvent.VK_LEFT, 2, 46); + sendKeyInCurrentPosition(KeyEvent.VK_LEFT, 2, 26); + sendKeyInCurrentPosition(KeyEvent.VK_1, 2, 27); + updateArrowNavigationScreen(ArrowInput.ZERO, ArrowInput.ONE); + assertThat(xtn5250TerminalEmulator.getInputs()).isEqualTo(ArrowInput.ONE.inputs); + } + + @Test + public void shouldBuildUpAndLeftNavigationInputWhenSendingUpAndLeftArrow() { + updateArrowNavigationScreen(ArrowInput.ZERO, ArrowInput.ONE); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setCursor(2, 27); + when(characterBasedProtocolClient.getCursorPosition()) + .thenReturn(Optional.of(new Position(2, 27))) + .thenReturn(Optional.of(new Position(1, 27))) + .thenReturn(Optional.of(new Position(1, 27))) + .thenReturn(Optional.of(new Position(1, 26))) + .thenReturn(Optional.of(new Position(1, 26))) + .thenReturn(Optional.of(new Position(1, 27))); + sendKeyInCurrentPosition(KeyEvent.VK_UP, 1, 27); + sendKeyInCurrentPosition(KeyEvent.VK_LEFT, 1, 26); + updateArrowNavigationScreen(ArrowInput.ZERO, ArrowInput.ONE, ArrowInput.TWO); + sendKeyInCurrentPosition(KeyEvent.VK_2, 1, 27); + assertThat(xtn5250TerminalEmulator.getInputs()).isEqualTo(ArrowInput.TWO.inputs); + } + + @Test + public void shouldBuildRightNavigationInputWhenSendingRightArrow() { + updateArrowNavigationScreen(ArrowInput.ZERO, ArrowInput.ONE, ArrowInput.TWO); + setupInteractiveCharacterEmulator(); + xtn5250TerminalEmulator.setCursor(1, 27); + when(characterBasedProtocolClient.getCursorPosition()) + .thenReturn(Optional.of(new Position(1, 27))) + .thenReturn(Optional.of(new Position(1, 54))) + .thenReturn(Optional.of(new Position(1, 54))) + .thenReturn(Optional.of(new Position(1, 55))); + sendKeyInCurrentPosition(KeyEvent.VK_RIGHT, 1, 54); + updateArrowNavigationScreen(ArrowInput.ZERO, ArrowInput.ONE, ArrowInput.TWO, ArrowInput.THREE); + sendKeyInCurrentPosition(KeyEvent.VK_3, 1, 55); + assertThat(xtn5250TerminalEmulator.getInputs()).isEqualTo(ArrowInput.THREE.inputs); + } + + @Test + public void shouldProperBuildMixedInputsWhenFirstPositionIsMiddleField() throws Exception { + setScreenWithUserNameAndPasswordFields(); + xtn5250TerminalEmulator.setCursor(2, 18); + sendNavigationKey(KeyEvent.VK_TAB, 0); + sendKeyWithCursorUpdate(KeyEvent.VK_T, 0, 1, 14); + xtn5250TerminalEmulator.setSelectedArea(new Rectangle(0, 0, 11, 1)); + clickButton(INPUT_BY_LABEL_BUTTON); + sendKeyWithCursorUpdate(KeyEvent.VK_Y, 0, 2, 18); + assertThat(xtn5250TerminalEmulator.getInputs()).isEqualTo(Arrays.asList(new NavigationInput(0 + , NavigationType.TAB, "y"), new LabelInput("Insert Name", "t"))); + + } + + private void sendNavigationKey(int key, int modifiers) { + Component focusedComponent = frame.robot().finder().find(Component::isFocusOwner); + JComponentDriver driver = new JComponentDriver(frame.robot()); + driver.pressAndReleaseKey(focusedComponent, KeyPressInfo.keyCode(key).modifiers(modifiers)); + } + + public enum ArrowInput { + ZERO("0", Collections.singletonList(new NavigationInput(1, NavigationType.DOWN, "0"))), + ONE("1", Collections.singletonList(new NavigationInput(2, NavigationType.LEFT, "1"))), + TWO("2", Arrays.asList(new NavigationInput(1, NavigationType.UP, ""), + new NavigationInput(1, NavigationType.LEFT, "2"))), + THREE("3", Collections.singletonList(new NavigationInput(1, NavigationType.RIGHT, "3"))); + + public String value; + public List inputs; + + ArrowInput(String value, List inputs) { + this.value = value; + this.inputs = inputs; + } + + @Override + public String toString() { + return value; + } } } diff --git a/src/test/java/com/blazemeter/jmeter/rte/sampler/RTESamplerTest.java b/src/test/java/com/blazemeter/jmeter/rte/sampler/RTESamplerTest.java index 8b1f9ad1..2ee7da0c 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/sampler/RTESamplerTest.java +++ b/src/test/java/com/blazemeter/jmeter/rte/sampler/RTESamplerTest.java @@ -79,7 +79,7 @@ public static void tearDownClass() { @Before public void setup() { rteSampler = new RTESampler(p -> client); - when(client.isInputInhibited()).thenReturn(true); + when(client.isInputInhibited()).thenReturn(Optional.of(true)); when(client.getScreen()).thenReturn(Screen.valueOf(TEST_SCREEN)); when(client.resetAlarm()).thenReturn(false); when(client.getCursorPosition()).thenReturn(Optional.of(CURSOR_POSITION)); @@ -155,7 +155,7 @@ private RteSampleResultBuilder buildErrorResultBuilder(Exception e) { public void shouldGetErrorSamplerResultWhenSendThrowIllegalArgumentException() throws Exception { IllegalArgumentException e = new IllegalArgumentException(); doThrow(e).when(client) - .send(INPUTS, AttentionKey.ENTER); + .send(INPUTS, AttentionKey.ENTER, RTESampler.DEFAULT_CONNECTION_TIMEOUT_MILLIS); SampleResult expected = buildErrorResultBuilder(e) .withInputInhibitedRequest(true) .withInputs(INPUTS) @@ -170,8 +170,7 @@ public void shouldGetErrorSamplerResultWhenSendThrowIllegalArgumentException() t private void truncateExceptionStacktrace(SampleResult result) { String response = result.getResponseDataAsString(); - result.setResponseData(response.substring(0, response.indexOf('\n')), - StandardCharsets.UTF_8.name()); + result.setResponseData(response.substring(0, response.indexOf('\n')), StandardCharsets.UTF_8.name()); } @Test @@ -221,7 +220,7 @@ public void shouldSendDefaultAttentionKeyToEmulatorWhenSampleWithoutSpecifyingAt throws Exception { rteSampler.sample(null); verify(client) - .send(anyList(), eq(AttentionKey.ENTER)); + .send(anyList(), eq(AttentionKey.ENTER), anyLong()); } @Test @@ -230,7 +229,7 @@ public void shouldSendCustomAttentionKeyToEmulatorWhenSampleWithCustomAttentionK rteSampler.setAttentionKey(AttentionKey.F1); rteSampler.sample(null); verify(client) - .send(anyList(), eq(AttentionKey.F1)); + .send(anyList(), eq(AttentionKey.F1), anyLong()); } @Test @@ -238,7 +237,7 @@ public void shouldNotSendInputToEmulatorWhenSampleWithConnectAction() throws Exc rteSampler.setAction(Action.CONNECT); rteSampler.sample(null); verify(client, never()) - .send(anyList(), any(AttentionKey.class)); + .send(anyList(), any(AttentionKey.class), anyLong()); } @Test @@ -512,7 +511,7 @@ public void shouldSendInputsToProtocolClientWhenSampleAfterSetInputs() throws Rt rteSampler.setAttentionKey(AttentionKey.ENTER); rteSampler.setInputs(INPUTS); rteSampler.sample(null); - verify(client).send(INPUTS, AttentionKey.ENTER); + verify(client).send(INPUTS, AttentionKey.ENTER, RTESampler.DEFAULT_CONNECTION_TIMEOUT_MILLIS); } } diff --git a/src/test/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanelIT.java b/src/test/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanelIT.java index a99a1fc7..8149286a 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanelIT.java +++ b/src/test/java/com/blazemeter/jmeter/rte/sampler/gui/InputPanelIT.java @@ -8,10 +8,12 @@ import com.blazemeter.jmeter.rte.core.CoordInput; import com.blazemeter.jmeter.rte.core.Input; import com.blazemeter.jmeter.rte.core.LabelInput; +import com.blazemeter.jmeter.rte.core.NavigationInput; import com.blazemeter.jmeter.rte.core.Position; -import com.blazemeter.jmeter.rte.core.TabulatorInput; import com.blazemeter.jmeter.rte.sampler.InputTestElement; import com.blazemeter.jmeter.rte.sampler.Inputs; +import com.blazemeter.jmeter.rte.sampler.NavigationInputRowGui; +import com.blazemeter.jmeter.rte.sampler.NavigationType; import com.blazemeter.jmeter.rte.sampler.gui.InputPanel.FieldPanel; import java.awt.Component; import java.awt.Point; @@ -34,7 +36,6 @@ import org.assertj.swing.fixture.JPanelFixture; import org.assertj.swing.fixture.JTableCellFixture; import org.assertj.swing.fixture.JTableFixture; -import org.assertj.swing.fixture.JTextComponentFixture; import org.assertj.swing.timing.Condition; import org.junit.After; import org.junit.Before; @@ -43,7 +44,7 @@ public class InputPanelIT { - private static final long CHANGE_TIMEOUT_MILLIS = 20000; + private static final long CHANGE_TIMEOUT_MILLIS = 10000; private static final LabelInput USER_LABEL_INPUT = new LabelInput("User", "TESTUSR"); private static final CoordInput PASS_COORD_INPUT = new CoordInput(new Position(1, 2), "TESTPSW"); private static final CoordInput NAME_INPUT = new CoordInput(new Position(4, 2), "TESTNAME"); @@ -52,6 +53,7 @@ public class InputPanelIT { private static final String UP_BUTTON = "upButton"; private static final String DOWN_BUTTON = "downButton"; private static final String TEST = "TEST"; + private static final String COMBO_TYPE = "comboType"; private FrameFixture frame; private InputPanel panel; @@ -62,8 +64,8 @@ public static void setupClass() { JMeterTestUtils.setupJmeterEnv(); } - private static TabulatorInput TABULATOR_INPUT(int offset) { - return new TabulatorInput(offset, TEST); + private static NavigationInput NAVIGATION_INPUT(int offset, NavigationType type) { + return new NavigationInput(offset, type, TEST); } @Before @@ -86,13 +88,13 @@ public void shouldAddLabelInputWhenClickAddLabel() { } private void addFieldByLabel(int row, LabelInput input) { - selectFromInputComboBox(0); + frame.comboBox(COMBO_TYPE).selectItem(0); + clickAddButton(); awaitAddedRow(row); setInputRow(row, input, (panel, i) -> setTextField(panel, "fieldLabel", i.getLabel())); } - private void selectFromInputComboBox(int type) { - frame.comboBox("comboType").selectItem(type); + private void clickAddButton() { frame.button("addButton").click(); } @@ -109,8 +111,7 @@ public boolean test() { private void setInputRow(int row, T input, BiConsumer fieldSetter) { - JTableCellFixture fieldCell = inputTable - .cell(TableCell.row(row).column(0)); + JTableCellFixture fieldCell = inputTable.cell(TableCell.row(row).column(0)); fieldCell.startEditing(); JPanelFixture fieldPanel = new JPanelFixture(frame.robot(), (FieldPanel) fieldCell.editor()); fieldSetter.accept(fieldPanel, input); @@ -123,9 +124,7 @@ private void setInputRow(int row, T input, } private void setTextField(JPanelFixture fieldPanel, String name, String text) { - JTextComponentFixture textField = fieldPanel.textBox(name); - frame.robot().click(textField.target()); - textField.setText(text); + fieldPanel.textBox(name).setText(text); } private void assertInputs(Input... inputs) { @@ -167,7 +166,8 @@ public void shouldAddCoordInputWhenClickAddCoord() { } private void addFieldByCoord(int row, CoordInput input) { - selectFromInputComboBox(2); + frame.comboBox(COMBO_TYPE).selectItem(2); + clickAddButton(); awaitAddedRow(row); setInputRow(row, input, (panel, i) -> { Position position = i.getPosition(); @@ -289,38 +289,34 @@ private void setTextToClipboard(String s) { } private void clickAddFromClipboard() { - selectFromInputComboBox(3); + frame.comboBox(COMBO_TYPE).selectItem(3); + clickAddButton(); } @Test public void shouldAddInputsWhenCopyFromClipboard() { - setTextToClipboard("TEST\nUser\tTESTUSR\n1\t2\tTESTPSW\n1\t4\tTESTNAME\tNAME"); + setTextToClipboard("TEST\n" + + "User\tTESTUSR\n" + + "1\t2\tTESTPSW\n" + + "1\t4\tTESTNAME\tNAME\n" + + "\tTEST\n" + + "\tTEST\n" + + "\tTEST\n" + + "\tTEST\n" + + "\tTEST"); clickAddFromClipboard(); - TabulatorInput tabulatorInput = new TabulatorInput(0, TEST); CoordInput name = new CoordInput(new Position(1, 4), "TESTNAME"); - assertInputs(tabulatorInput, USER_LABEL_INPUT, PASS_COORD_INPUT, name); - } - - @Test - public void shouldAddInputByTabAndInputByLabelWhenCopyFromClipboard() { - setTextToClipboard("\tTEST\nUser\tTESTUSR\n"); - clickAddFromClipboard(); - assertInputs(TABULATOR_INPUT(3), USER_LABEL_INPUT); + assertInputs(NAVIGATION_INPUT(0, NavigationType.TAB), USER_LABEL_INPUT, PASS_COORD_INPUT, + name, NAVIGATION_INPUT(1, NavigationType.TAB), NAVIGATION_INPUT(2, NavigationType.UP), + NAVIGATION_INPUT(2, NavigationType.DOWN), NAVIGATION_INPUT(3, NavigationType.RIGHT), + NAVIGATION_INPUT(10, NavigationType.LEFT)); } @Test public void shouldAddDefaultInputWhenNoTabsFoundInCopyFromClipboard() { setTextToClipboard("TEST\n"); clickAddFromClipboard(); - TabulatorInput tabulatorInput = new TabulatorInput(0, TEST); - assertInputs(tabulatorInput); - } - - @Test - public void shouldAddTabInputWhenCopyFromClipboardInExtendedVersion() { - setTextToClipboard("\tTEST"); - clickAddFromClipboard(); - assertInputs(TABULATOR_INPUT(3)); + assertInputs(NAVIGATION_INPUT(0, NavigationType.TAB)); } @Test @@ -380,16 +376,48 @@ public void shouldDisableDownButtonWhenOneInputIsLeft() { } @Test - public void shouldAddTabulatorInput() { - addFiledByTab(0, TABULATOR_INPUT(4)); - assertInputs(TABULATOR_INPUT(4)); + public void shouldAddTabNavigationInput() { + addNavigationInput(0, NAVIGATION_INPUT(4, NavigationType.TAB)); + assertInputs(NAVIGATION_INPUT(4, NavigationType.TAB)); } - private void addFiledByTab(int row, TabulatorInput input) { - selectFromInputComboBox(1); + private void addNavigationInput(int row, NavigationInput input) { + frame.comboBox(COMBO_TYPE).selectItem(1); + clickAddButton(); awaitAddedRow(row); + selectNavigationTypeInCombo(input.getNavigationType(), row); setInputRow(row, input, - (panel, i) -> setTextField(panel, "fieldTabulator", String.valueOf(i.getOffset()))); + (panel, i) -> setTextField(panel, "fieldNavigation", String.valueOf(i.getRepeat()))); + awaitAddedNavigationType(input.getNavigationType(), row); + } + + private void awaitAddedNavigationType(NavigationType navigationType, int row) { + pause(new Condition("waiting for " + navigationType + " to be added in ") { + @Override + public boolean test() { + return ((NavigationInputRowGui) inputTable.target().getModel().getValueAt(row, + 0)).getTypeNavigation().equals(navigationType); + } + }); + } + + private void selectNavigationTypeInCombo( + NavigationType navigationType, int row) { + JTableCellFixture fieldCell = inputTable.cell(TableCell.row(row).column(0)); + fieldCell.startEditing(); + JPanelFixture fieldPanel = new JPanelFixture(frame.robot(), (FieldPanel) fieldCell.editor()); + fieldPanel.comboBox("navigationCombo").selectItem(navigationType.getLabel()); + fieldCell.stopEditing(); + } + + @Test + public void shouldAddArrowNavigationInputs() { + addNavigationInput(0, NAVIGATION_INPUT(4, NavigationType.LEFT)); + addNavigationInput(1, NAVIGATION_INPUT(3, NavigationType.UP)); + addNavigationInput(2, NAVIGATION_INPUT(2, NavigationType.RIGHT)); + addNavigationInput(3, NAVIGATION_INPUT(1, NavigationType.DOWN)); + assertInputs(NAVIGATION_INPUT(4, NavigationType.LEFT), NAVIGATION_INPUT(3, NavigationType.UP) + , NAVIGATION_INPUT(2, NavigationType.RIGHT), NAVIGATION_INPUT(1, NavigationType.DOWN)); } private static class FieldWriter extends AbstractJTableCellWriter { diff --git a/src/test/java/com/blazemeter/jmeter/rte/waitsRecorder/SyncWaitConditionRecorderTest.java b/src/test/java/com/blazemeter/jmeter/rte/waitsRecorder/SyncWaitConditionRecorderTest.java index 5739c5c3..0152e651 100644 --- a/src/test/java/com/blazemeter/jmeter/rte/waitsRecorder/SyncWaitConditionRecorderTest.java +++ b/src/test/java/com/blazemeter/jmeter/rte/waitsRecorder/SyncWaitConditionRecorderTest.java @@ -19,7 +19,8 @@ @RunWith(MockitoJUnitRunner.class) public class SyncWaitConditionRecorderTest { - + private static final Optional FALSE = Optional.of(false); + private static final Optional TRUE = Optional.of(true); private final long STABLE_PERIOD_MILLIS = 1000L; private final long TIMEOUT_THRESHOLD_MILLIS = 10000L; private final static long CLOCK_STEP_MILLIS = 400L; @@ -34,7 +35,7 @@ public void setup() { when(clock.instant()).thenReturn(Instant.now()); syncWaitRecorder = new SyncWaitRecorder(rteProtocolClientMock, TIMEOUT_THRESHOLD_MILLIS, STABLE_PERIOD_MILLIS, clock); - when(rteProtocolClientMock.isInputInhibited()).thenReturn(true, false, true, false); + when(rteProtocolClientMock.isInputInhibited()).thenReturn(TRUE, FALSE, TRUE, FALSE); startTime = clock.instant(); when(clock.instant()).thenReturn(startTime.plusMillis(CLOCK_STEP_MILLIS), startTime.plusMillis(CLOCK_STEP_MILLIS * 2), @@ -56,7 +57,7 @@ public void shouldReturnEmptyWhenMaxInputInhibitedIsBiggerThanStablePeriod() { @Test public void shouldReturnEmptyIfIsLastInputInhibitedWhenStop() { - when(rteProtocolClientMock.isInputInhibited()).thenReturn(false, true, false, true); + when(rteProtocolClientMock.isInputInhibited()).thenReturn(FALSE, TRUE, FALSE, TRUE); syncWaitRecorder.start(); syncWaitRecorder.onTerminalStateChange(); syncWaitRecorder.onTerminalStateChange(); @@ -65,7 +66,7 @@ public void shouldReturnEmptyIfIsLastInputInhibitedWhenStop() { @Test public void shouldGetExpectedIfIsNotFirstInputInhibitedWhenTerminalStateChangesOnce() { - when(rteProtocolClientMock.isInputInhibited()).thenReturn(false, true, false); + when(rteProtocolClientMock.isInputInhibited()).thenReturn(FALSE, TRUE, FALSE); syncWaitRecorder.start(); syncWaitRecorder.onTerminalStateChange(); assertEquals(Optional.of(new SyncWaitCondition( @@ -91,7 +92,7 @@ private WaitCondition buildExpectedWaitConditionWithNormalFlowOfInputsInhibited( public void shouldGetWaitConditionIgnoringNonKeyboardStatusChangesWhenStop() { syncWaitRecorder.start(); syncWaitRecorder.onTerminalStateChange(); - when(rteProtocolClientMock.isInputInhibited()).thenReturn(false, true, false); + when(rteProtocolClientMock.isInputInhibited()).thenReturn(FALSE, TRUE, FALSE); syncWaitRecorder.onTerminalStateChange(); when(clock.instant()).thenReturn(startTime.plusMillis(CLOCK_STEP_MILLIS * 4)); syncWaitRecorder.onTerminalStateChange(); diff --git a/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/arrow-navigation-screen.html b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/arrow-navigation-screen.html new file mode 100644 index 00000000..65d40803 --- /dev/null +++ b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/arrow-navigation-screen.html @@ -0,0 +1,25 @@ +
              USER NAME:       PERMISSIONS REQUIRED:                            
+              USER TYPE:       INTERNAL CODE:                                   
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+
\ No newline at end of file diff --git a/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/login-success-screen.html b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/login-success-screen.html new file mode 100644 index 00000000..b7a279d5 --- /dev/null +++ b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/login-success-screen.html @@ -0,0 +1,26 @@ + +
                                  END OF FLOW                                   
+                                                                                
+                                                                                
+ Press enter to exit:                                                           
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+
\ No newline at end of file diff --git a/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/login.yml b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/login.yml new file mode 100644 index 00000000..e0775cba --- /dev/null +++ b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/login.yml @@ -0,0 +1,86 @@ +- !server {data: FFFB03FFFB01} +- !client {data: FFFD03} +- !client {data: FFFD01} +- !server {data: 1B5B6D1B5B481B5B4A1B5B313B31481B5B6D1B2842202020201B5B313B3248205F5F20202020205F5F20205F5F5F5F5F5F5F5F20205F5F202020205F5F2020205F5F5F5F5F5F202020205F5F5F5F5F5F20201B5B323B32482F20207C2020202F20207C2F20202020202020207C2F20207C20202F20207C202F2020202020205C20202F2020202020205C201B5B333B32482424207C2020202424207C24242424242424242F202424207C20202424207C2F24242424242420207C2F24242424242420207C1B5B343B32482424207C2020202424207C2020202424207C2020202424207C5F5F2424207C24245F5F5F5F2424207C24242420205C2424207C1B5B353B3248242420205C202F24242F202020202424207C2020202424202020202424207C202F2020202024242F202424242420202424207C1B5B363B324820242420202F24242F20202020202424207C2020202424242424242424207C2F2424242424242F20202424202424202424207C1B5B373B3248202024242024242F2020202020202424207C2020202020202020202424207C2424207C5F5F5F5F5F202424205C24242424207C1B5B383B32482020202424242F202020202020202424207C2020202020202020202424207C2424202020202020207C24242020202424242F201B5B393B324820202020242F202020202020202024242F2020202020202020202024242F2024242424242424242F20202424242424242F20201B5B31323B3248507265737320656E74657220746F20636F6E74696E75653A201B5B31323B323748, delayMillis: 900} +- !client {data: 0D00} +- !server {data: 1B5B6D1B5B481B5B4A1B5B31323B3432481B5B313B3148201B5B313B3148202020201B5B6D1B2842201B5B323B3330481B5B6D201B5B3B316D1B28422A2A571B5B323B333548451B5B323B3337484C1B5B323B333948431B5B323B3431484F1B5B323B3433484D1B5B323B343548451B5B323B3437482A2A1B5B6D1B5B6D1B2842201B5B343B333648205741524E494E47201B5B363B3230482054484953204D4154455249414C1B5B363B3335484954204953204A55535420464F521B5B363B35304854455354494E472050524F504F55534553201B5B31303B333048205445535445521B5B31303B33384849443A1B5B6D201B5B31303B3435481B5B6D1B2842201B5B31323B323848444556454C4F5045521B5B31323B33384849443A1B5B6D201B5B31323B3434481B5B6D1B2842201B5B31343B33314820574F524B444154453A201B5B31343B343848201B5B31363B3331482050415353574F52443A1B5B6D201B5B31363B3438481B5B6D1B2842201B5B32323B33324820454E5445521B5B32323B34334843414E43454C201B5B31303B3432481B5B6D3030311B5B31323B3432481B5B31323B3434481B5B31343B3432481B5B31363B3432481B5B31363B3438481B5B313B31481B5B6D1B2842202020201B5B313B32481B5B313B3548201B5B313B31394820202020201B5B313B3235481B5B313B343648201B5B313B3532481B5B313B3748201B5B313B323548201B5B313B3330481B5B31323B343248, delayMillis: 900} + #Send developer_id 'tt' +- !client {data: '74'} +- !server {data: 1B5B6D54, delayMillis: 2} +- !client {data: '74'} +- !server {data: 541B5B31343B343248, delayMillis: 2} + #Send workdate '123456' +- !client {data: '31'} +- !server {data: 1B5B6D1B284231, delayMillis: 2} +- !client {data: '32'} +- !server {data: '32', delayMillis: 2} +- !client {data: '33'} +- !server {data: '33', delayMillis: 3} +- !client {data: '34'} +- !server {data: '34', delayMillis: 2} +- !client {data: '35'} +- !server {data: '35', delayMillis: 2} +- !client {data: '36'} +- !server {data: 361B5B31363B343248, delayMillis: 2} +- !client {data: '70'} +- !server {data: 1B5B6D20, delayMillis: 2} + #Send password 'passwd' +- !client {data: '61'} +- !server {data: '20', delayMillis: 2} +- !client {data: '73'} +- !server {data: '20', delayMillis: 2} +- !client {data: '73'} +- !server {data: '20', delayMillis: 2} +- !client {data: '77'} +- !server {data: '20', delayMillis: 2} +- !client {data: '64'} +- !server {data: 201B5B31303B343248, delayMillis: 2} +- !client {data: 0D00} +- !server {data: 1B5B6D1B5B481B5B4A1B5B343B3237481B5B313B3148201B5B313B3148202020201B5B6D1B28421B5B313B31354855534552204E414D453A201B5B313B3332485045524D495353494F4E532052455155495245443A201B5B323B3135485553455220545950453A201B5B323B333248494E5445524E414C20434F44453A201B5B313B353448, delayMillis: 300} + #Move a position down +- !client {data: 1B5B42} +- !server {data: 1B5B323B343848, delayMillis: 2} + #Write internal_code '00' +- !client {data: '30'} +- !server {data: '30', delayMillis: 3} +- !client {data: '30'} +- !server {data: '30', delayMillis: 3} + #Move tow positions left +- !client {data: 1B5B44} +- !server {data: 1B5B323B343848, delayMillis: 1} +- !client {data: 1B5B44} +- !server {data: 1B5B323B323648, delayMillis: 2} + #Send user_type 'dev' +- !client {data: '64'} +- !server {data: '64', delayMillis: 3} +- !client {data: '65'} +- !server {data: '65', delayMillis: 3} +- !client {data: '76'} +- !server {data: '76', delayMillis: 3} + #Move a position up +- !client {data: 1B5B41} +- !server {data: 1B5B313B323648, delayMillis: 10} + #Send user_name 'test' +- !client {data: '74'} +- !server {data: '74', delayMillis: 3} +- !client {data: '65'} +- !server {data: '65', delayMillis: 3} +- !client {data: '73'} +- !server {data: '73', delayMillis: 3} +- !client {data: '74'} +- !server {data: '74', delayMillis: 3} + #Move one position to the right +- !client {data: 1B5B43} +- !server {data: 1B5B313B353348} + #Send permission_required 'root' +- !client {data: '72'} +- !server {data: '72', delayMillis: 3} +- !client {data: '6F'} +- !server {data: '6F', delayMillis: 3} +- !client {data: '6F'} +- !server {data: '6F', delayMillis: 3} +- !client {data: '74'} +- !server {data: '74', delayMillis: 3} +- !client {data: 0D00} +- !server {data: 1B5B6D1B5B481B5B4A1B5B343B3237481B5B313B3148201B5B313B3148202020201B5B6D1B28421B5B313B333548454E44204F4620464C4F571B5B343B3248507265737320656E74657220746F20657869743A201B5B343B323748, delayMillis: 300} +- !client {data: 0D00} \ No newline at end of file diff --git a/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/slow-welcome-screen.yml b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/slow-welcome-screen.yml new file mode 100644 index 00000000..6551f2da --- /dev/null +++ b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/slow-welcome-screen.yml @@ -0,0 +1,36 @@ +- !server {data: FFFB03FFFB01} +- !client {data: FFFD03} +- !client {data: FFFD01} +- !server {delayMillis: 10000, data: 1B5B6D1B5B481B5B4A1B5B313B31481B5B6D1B2842202020201B5B313B3248205F5F20202020205F5F20205F5F5F5F5F5F5F5F20205F5F202020205F5F2020205F5F5F5F5F5F202020205F5F5F5F5F5F20201B5B323B32482F20207C2020202F20207C2F20202020202020207C2F20207C20202F20207C202F2020202020205C20202F2020202020205C201B5B333B32482424207C2020202424207C24242424242424242F202424207C20202424207C2F24242424242420207C2F24242424242420207C1B5B343B32482424207C2020202424207C2020202424207C2020202424207C5F5F2424207C24245F5F5F5F2424207C24242420205C2424207C1B5B353B3248242420205C202F24242F202020202424207C2020202424202020202424207C202F2020202024242F202424242420202424207C1B5B363B324820242420202F24242F20202020202424207C2020202424242424242424207C2F2424242424242F20202424202424202424207C1B5B373B3248202024242024242F2020202020202424207C2020202020202020202424207C2424207C5F5F5F5F5F202424205C24242424207C1B5B383B32482020202424242F202020202020202424207C2020202020202020202424207C2424202020202020207C24242020202424242F201B5B393B324820202020242F202020202020202024242F2020202020202020202024242F2024242424242424242F20202424242424242F20201B5B31323B3248507265737320656E74657220746F20636F6E74696E75653A201B5B31323B323748} +- !client {data: 0D00} +- !server {data: 1B5B6D1B5B481B5B4A1B5B31323B3432481B5B313B3148201B5B313B3148202020201B5B6D1B2842201B5B323B3330481B5B6D201B5B3B316D1B28422A2A571B5B323B333548451B5B323B3337484C1B5B323B333948431B5B323B3431484F1B5B323B3433484D1B5B323B343548451B5B323B3437482A2A1B5B6D1B5B6D1B2842201B5B343B333648205741524E494E47201B5B363B3230482054484953204D4154455249414C1B5B363B3335484954204953204A55535420464F521B5B363B35304854455354494E472050524F504F55534553201B5B31303B333048205445535445521B5B31303B33384849443A1B5B6D201B5B31303B3435481B5B6D1B2842201B5B31323B323848444556454C4F5045521B5B31323B33384849443A1B5B6D201B5B31323B3434481B5B6D1B2842201B5B31343B33314820574F524B444154453A201B5B31343B343848201B5B31363B3331482050415353574F52443A1B5B6D201B5B31363B3438481B5B6D1B2842201B5B32323B33324820454E5445521B5B32323B34334843414E43454C201B5B31303B3432481B5B6D3030311B5B31323B3432481B5B31323B3434481B5B31343B3432481B5B31363B3432481B5B31363B3438481B5B313B31481B5B6D1B2842202020201B5B313B32481B5B313B3548201B5B313B31394820202020201B5B313B3235481B5B313B343648201B5B313B3532481B5B313B3748201B5B313B323548201B5B313B3330481B5B31323B343248, delayMillis: 900} +- !client {data: '74'} +- !server {data: 1B5B6D54, delayMillis: 2} +- !client {data: '74'} +- !server {data: 541B5B31343B343248, delayMillis: 2} +- !client {data: '31'} +- !server {data: 1B5B6D1B284231, delayMillis: 2} +- !client {data: '32'} +- !server {data: '32', delayMillis: 2} +- !client {data: '33'} +- !server {data: '33', delayMillis: 3} +- !client {data: '34'} +- !server {data: '34', delayMillis: 2} +- !client {data: '35'} +- !server {data: '35', delayMillis: 2} +- !client {data: '36'} +- !server {data: 361B5B31363B343248, delayMillis: 2} +- !client {data: '70'} +#next field is the one telling the server that the next chars will be obfuscated +- !server {data: 1B5B6D20, delayMillis: 2} +- !client {data: '61'} +- !server {data: '20', delayMillis: 2} +- !client {data: '73'} +- !server {data: '20', delayMillis: 2} +- !client {data: '73'} +- !server {data: '20', delayMillis: 2} +- !client {data: '77'} +- !server {data: '20', delayMillis: 2} +- !client {data: '64'} +- !server {data: 201B5B31303B343248, delayMillis: 2} +- !client {data: 0D00} \ No newline at end of file diff --git a/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/user-menu-screen.html b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/user-menu-screen.html new file mode 100644 index 00000000..4c315a3f --- /dev/null +++ b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/user-menu-screen.html @@ -0,0 +1,25 @@ +
                                                                                
+                              **W E L C O M E **                                
+                                                                                
+                                    WARNING                                     
+                                                                                
+                    THIS MATERIAL IT IS JUST FOR TESTING PROPOUSES              
+                                                                                
+                                                                                
+                                                                                
+                              TESTER ID: 001                                    
+                                                                                
+                           DEVELOPER ID:                                        
+                                                                                
+                               WORKDATE:                                        
+                                                                                
+                               PASSWORD:                                        
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                ENTER     CANCEL                                
+                                                                                
+                                                                                
+
\ No newline at end of file diff --git a/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/user-welcome-screen.html b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/user-welcome-screen.html new file mode 100644 index 00000000..989dad8b --- /dev/null +++ b/src/test/resources/com/blazemeter/jmeter/rte/protocols/vt420/user-welcome-screen.html @@ -0,0 +1,25 @@ +
  __     __  ________  __    __   ______    ______                              
+ /  |   /  |/        |/  |  /  | /      \  /      \                             
+ $$ |   $$ |$$$$$$$$/ $$ |  $$ |/$$$$$$  |/$$$$$$  |                            
+ $$ |   $$ |   $$ |   $$ |__$$ |$$____$$ |$$$  \$$ |                            
+ $$  \ /$$/    $$ |   $$    $$ | /    $$/ $$$$  $$ |                            
+  $$  /$$/     $$ |   $$$$$$$$ |/$$$$$$/  $$ $$ $$ |                            
+   $$ $$/      $$ |         $$ |$$ |_____ $$ \$$$$ |                            
+    $$$/       $$ |         $$ |$$       |$$   $$$/                             
+     $/        $$/          $$/ $$$$$$$$/  $$$$$$/                              
+                                                                                
+                                                                                
+ Press enter to continue:                                                       
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+                                                                                
+
\ No newline at end of file