Skip to content

Commit

Permalink
Send attention key at determined position
Browse files Browse the repository at this point in the history
Additional task tackled:
• Fix sample naming issue: sample name was continuously changing when using VT420 alongside with the recorder.
• Fix sending attention key when screen was not constituted by fields. (Threw IndexOutOfBounds -1)
  • Loading branch information
Baraujo25 committed Aug 18, 2020
1 parent ca03189 commit 9f50d05
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 70 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ The RTE Sampler fields are:
- *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 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.
- **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
Expand All @@ -104,7 +104,12 @@ The RTE Sampler fields are:
- ***\<LEFT>\<LEFT>:*** It will create a left arrow navigation with a double repetition of the left key before sending the input value. Which is equivalent to *<LEFT\*2>*.
- ***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'.

- *Positioning cursor*
- It is possible to set the cursor position to a desired position. This could be useful to **send an attention key to a determined position**. In order to set the cursor position, we will need to add a Coordinate Input on our send-keys payload sampler. The Coordinate Input will have set the position where we want to set the cursor, and the value to be sent to the mainframe must be empty.
- Example of sending an Enter Attention Key at position (1, 27):
![alt text](docs/send-attention-key-at-position.png)
> This functionality is supported for the recorder automatically. If cursor position is not on the default place (after typing on a field) and an Attention Key is triggered, the recorder will add the Coordinate Input to set the cursor position where the cursor was left while interacting with the emulator.
- *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.
Expand Down
Binary file added docs/send-attention-key-at-position.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>com.blazemeter.jmeter</groupId>
<artifactId>jmeter-bzm-rte</artifactId>
<packaging>jar</packaging>
<version>3.0.1</version>
<version>3.1.0</version>
<name>RTEPlugin Sampler as JMeter plugin</name>

<properties>
Expand Down Expand Up @@ -35,7 +35,7 @@
<dependency>
<groupId>com.github.blazemeter</groupId>
<artifactId>xtn5250</artifactId>
<version>3.2.1</version>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.github.blazemeter</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ protected void setField(Input i, long echoTimeoutMillis) {
}

private void setFieldByCoord(CoordInput i) {
if (i.getInput().isEmpty()) {
int linearPosition =
(i.getPosition().getRow() - 1) * getScreenSize()
.width + i.getPosition().getColumn() - 1;
client.setCursorPosition(linearPosition);
return;
}
try {
client.setFieldTextByCoord(i.getPosition().getRow(),
i.getPosition().getColumn(), i.getInput());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ protected void setField(Input i, long echoTimeoutMillis) {
}

private void setFieldByCoord(CoordInput i) {
if (i.getInput().isEmpty()) {
client.setCursorPosition(i.getPosition().getRow() - 1, i.getPosition().getColumn() - 1);
return;
}
try {
client.setFieldTextByCoord(i.getPosition().getRow(),
i.getPosition().getColumn(), i.getInput());
Expand Down
72 changes: 41 additions & 31 deletions src/main/java/com/blazemeter/jmeter/rte/recorder/RTERecorder.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,20 @@ public class RTERecorder extends GenericController implements TerminalEmulatorLi
private transient JMeterTreeModel treeModelMock;
private transient TerminalEmulator terminalEmulator;
private transient Supplier<TerminalEmulator> terminalEmulatorSupplier;
private transient RecordingTargetFinder finder;
private final transient RecordingTargetFinder finder;
private transient JMeterTreeNode samplersTargetNode;
private transient RecordingStateListener recordingListener;

private transient String samplerName;
private transient RteSampleResultBuilder resultBuilder;
private transient RTESampler sampler;
private transient RequestListener requestListener;
private transient RequestListener<?> requestListener;
private transient int sampleCount;
private transient WaitConditionsRecorder waitConditionsRecorder;
private transient ExecutorService connectionExecutor;
private transient Function<List<Input>, List<Input>> inputProvider;
private transient Function<Protocol, RteProtocolClient> protocolFactory;
private final transient Function<Protocol, RteProtocolClient> protocolFactory;
private transient RteProtocolClient terminalClient;
private transient List<TestElement> responseAssertions = new ArrayList<>();
private final transient List<TestElement> responseAssertions = new ArrayList<>();

public RTERecorder() {
this(new RecordingTargetFinder(),
Expand Down Expand Up @@ -207,8 +206,9 @@ public void onRecordingStart() {
.findTargetControllerNode(getJmeterTreeModel());
addTestElementToTestPlan(buildRteConfigElement(), responseAssertions, samplersTargetNode);
notifyChildren(TestStateListener.class, TestStateListener::testStarted);
resultBuilder = buildSampleResultBuilder(Action.CONNECT);
sampler = buildSampler(Action.CONNECT, null, null);
String sampleName = buildSampleName(Action.CONNECT);
resultBuilder = buildSampleResultBuilder(Action.CONNECT, sampleName);
sampler = buildSampler(Action.CONNECT, null, null, sampleName);
terminalClient = protocolFactory.apply(getProtocol());
TerminalType terminalType = getTerminalType();
waitConditionsRecorder = new WaitConditionsRecorder(terminalClient,
Expand Down Expand Up @@ -241,9 +241,10 @@ threads connecting (on start, stop and start)
private void initTerminalEmulatorSupplier() {
//verification done in order to not override the mock when testing
if (terminalEmulatorSupplier.get() == null) {
inputProvider = inputs -> terminalClient instanceof CharacterBasedProtocolClient ? Collections
boolean isCharacterBased = terminalClient instanceof CharacterBasedProtocolClient;
inputProvider = inputs -> isCharacterBased ? Collections
.emptyList() : inputs;
if (terminalClient instanceof CharacterBasedProtocolClient) {
if (isCharacterBased) {
CharacterBasedEmulator characterBasedEmulator = new CharacterBasedEmulator();
terminalEmulatorSupplier = () -> new Xtn5250TerminalEmulator(characterBasedEmulator);
((CharacterBasedProtocolClient) terminalClient)
Expand Down Expand Up @@ -315,9 +316,9 @@ private <T> void notifyChildren(Class<T> classFilter, Consumer<T> notificationMe
}
}

private RteSampleResultBuilder buildSampleResultBuilder(Action action) {
private RteSampleResultBuilder buildSampleResultBuilder(Action action, String name) {
RteSampleResultBuilder ret = new RteSampleResultBuilder()
.withLabel(getSampleName(action))
.withLabel(name)
.withServer(getServer())
.withPort(getPort())
.withProtocol(getProtocol())
Expand All @@ -330,19 +331,12 @@ private RteSampleResultBuilder buildSampleResultBuilder(Action action) {
return ret;
}

private String getSampleName(Action action) {
return action == Action.SEND_INPUT ? samplerName : buildSampleName(action);
}

private String buildSampleName(Action action) {
return "bzm-RTE-" + action + (action == Action.SEND_INPUT ? "-" + (sampleCount + 1) : "");
}

private RTESampler buildSampler(Action action, List<Input> inputs, AttentionKey attentionKey) {
private RTESampler buildSampler(Action action, List<Input> inputs, AttentionKey attentionKey,
String screenName) {
RTESampler sampler = new RTESampler();
sampler.setProperty(TestElement.GUI_CLASS, RTESamplerGui.class.getName());
sampler.setProperty(TestElement.TEST_CLASS, RTESampler.class.getName());
sampler.setName(getSampleName(action));
sampler.setName(screenName);
sampler.setAction(action);
if (inputs != null) {
sampler.setInputs(inputs);
Expand All @@ -362,6 +356,7 @@ private void initTerminalEmulator(TerminalType terminalType) {
terminalEmulator.setSupportedAttentionKeys(terminalClient.getSupportedAttentionKeys());
terminalEmulator.setProtocolClient(terminalClient);
terminalEmulator.start();
terminalEmulator.setScreenName(sampler.getName());
terminalClient.addTerminalStateListener(this);

onTerminalStateChange();
Expand All @@ -374,14 +369,17 @@ private void registerRequestListenerFor() {

@Override
public void onAttentionKey(AttentionKey attentionKey, List<Input> inputs, String screenName) {
samplerName = screenName;
sampleCount++;
requestListener.stop();
recordPendingSample();
requestListener.stop();
terminalClient.resetAlarm();
resultBuilder = buildSendInputSampleResultBuilder(attentionKey, inputs);
String sampleName = buildSampleName(Action.SEND_INPUT);
terminalEmulator.setScreenName(sampleName);
resultBuilder = buildSendInputSampleResultBuilder(attentionKey, inputs, sampleName);
registerRequestListenerFor();
sampler = buildSampler(Action.SEND_INPUT, inputs, attentionKey);
sampler = buildSampler(Action.SEND_INPUT, inputs, attentionKey, sampleName);
resultBuilder.withLabel(screenName);
sampler.setName(screenName);
try {
waitConditionsRecorder.start();
terminalClient.send(inputProvider.apply(inputs), attentionKey,
Expand Down Expand Up @@ -425,21 +423,26 @@ private void notifySampleOccurred() {
}

private RteSampleResultBuilder buildSendInputSampleResultBuilder(AttentionKey attentionKey,
List<Input> inputs) {
return buildSampleResultBuilder(Action.SEND_INPUT)
List<Input> inputs, String name) {
return buildSampleResultBuilder(Action.SEND_INPUT, name)
.withInputs(inputs)
.withAttentionKey(attentionKey);
}

private String buildSampleName(Action action) {
return "bzm-RTE-" + action + (action == Action.SEND_INPUT ? "-" + (sampleCount + 1) : "");
}

@Override
public void onRecordingStop() {
LOG.debug("Stopping recording");
connectionExecutor.shutdownNow();
synchronized (this) {
if (terminalEmulator != null) {
recordPendingSample();
resultBuilder = buildSampleResultBuilder(Action.DISCONNECT);
sampler = buildSampler(Action.DISCONNECT, null, null);
String sampleName = buildSampleName(Action.DISCONNECT);
resultBuilder = buildSampleResultBuilder(Action.DISCONNECT, sampleName);
sampler = buildSampler(Action.DISCONNECT, null, null, sampleName);
terminalEmulator.stop();
terminalEmulator = null;
terminalEmulatorSupplier = () -> null;
Expand Down Expand Up @@ -478,8 +481,15 @@ public void onCloseTerminal() {

@Override
public void onTerminalStateChange() {
samplerName = buildSampleName(Action.SEND_INPUT);
terminalEmulator.setScreen(terminalClient.getScreen(), samplerName);
/*
Due to incorporation of VT protocol where the screen is changing constantly
without attention keys pressed, it is not possible to update screen name every time
terminal state changed. Therefore, first screen sampler name is placed manually.
*/
if (sampleCount == 0) {
terminalEmulator.setScreenName(buildSampleName(Action.SEND_INPUT));
}
terminalEmulator.setScreen(terminalClient.getScreen());
terminalClient.getCursorPosition().ifPresent(cursorPosition -> terminalEmulator
.setCursor(cursorPosition.getRow(), cursorPosition.getColumn()));
if (terminalClient.isInputInhibited().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class CharacterBasedEmulator extends
private static final Logger LOG = LoggerFactory.getLogger(CharacterBasedEmulator.class);
private Position lastCursorPosition;
private StringBuilder inputBuffer = new StringBuilder();
private List<Input> inputs = new ArrayList<>();
private final List<Input> inputs = new ArrayList<>();
private int repetition;
private Screen lastTerminalScreen;
private boolean isAreaSelected;
Expand Down Expand Up @@ -113,8 +113,8 @@ public synchronized void makePaste() {

if (!sequencesInClipboard.isEmpty()) {
String chunkAppearances = CharacterSequenceScaper.getSequenceChunkAppearancesIn(value);
JOptionPane.showMessageDialog(this, "Clipboard content \'" + String.join(", ",
sequencesInClipboard) + "\' is not "
JOptionPane.showMessageDialog(this, "Clipboard content '" + String.join(", ",
sequencesInClipboard) + "' is not "
+ "supported when pasting. \nAppearances of sequences near to: "
+ chunkAppearances, "Paste error",
JOptionPane.INFORMATION_MESSAGE);
Expand Down Expand Up @@ -296,4 +296,5 @@ protected void processMouseMotionEvent(MouseEvent e) {
}
super.processMouseMotionEvent(e);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ public static String getSequenceChunkAppearancesIn(String value) {
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();
int chunkBegin = Math.max(index - CHUNK_TEXT_SIZE, 0);
int chunkEnd = Math.min(index + CHUNK_TEXT_SIZE, value.length());
String chunk = value.substring(chunkBegin, chunkEnd);
sequenceLocation.append(chunkBegin == 0 ? "\n" : "\n...");
sequenceLocation.append(chunk);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.blazemeter.jmeter.rte.recorder.emulator;

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;
Expand Down Expand Up @@ -40,13 +41,21 @@ protected List<Input> getInputFields() {
fieldFromPos = getNextFieldFromPos(initialColumn, initialRow);
}
int initialField = getFields().indexOf(fieldFromPos);
//means screen is not constituted by fields
if (initialField == -1) {
fields.add(new CoordInput(new Position(getCursorRow() + 1, getCursorCol() + 1), ""));
labelMap.clear();
return fields;
}
Iterator<XI5250Field> it = getFields().listIterator(initialField);
int index = 0;
int offset = 1;
Position lastFieldPosition = null;
while (it.hasNext() && index < getFields().size()) {
XI5250Field f = it.next();
if (f.isMDTOn()) {
Position fieldPosition = new Position(f.getRow() + 1, f.getCol() + 1);
lastFieldPosition = fieldPosition;
String label = (String) labelMap.get(fieldPosition);
String trimmedInput = trimNulls(f.getString());
fields.add(label != null ? new LabelInput(label, trimmedInput)
Expand All @@ -61,10 +70,44 @@ protected List<Input> getInputFields() {
it = getFields().iterator();
}
}

if (isNeededToUpdateCursorPosition(lastFieldPosition)) {
fields.add(new CoordInput(new Position(getCursorRow() + 1, getCursorCol() + 1), ""));
}
labelMap.clear();
return fields;
}

private boolean isNeededToUpdateCursorPosition(
Position lastFieldPosition) {
if (lastFieldPosition == null) {
return true;
}
XI5250Field lastModifiedField = getFieldFromPos(lastFieldPosition.getColumn() - 1,
lastFieldPosition.getRow() - 1);
int fieldWrittenTextLength = lastModifiedField.getTrimmedString().length();
if (new Position(lastModifiedField.getRow(),
lastModifiedField.getCol() + fieldWrittenTextLength)
.equals(new Position(getCursorRow(), getCursorCol()))) {
return false;
}

XI5250Field nextField = getNextFieldAfterField(lastModifiedField);
Position currentCursorPosition = new Position(getCursorRow(), getCursorCol());
//when reaching the maximum length of a field while typing, emulator jumps to next field
return lastModifiedField.getLength() != fieldWrittenTextLength || !currentCursorPosition
.equals(new Position(nextField.getRow() + 1, nextField.getCol() + 1));
}

private XI5250Field getNextFieldAfterField(XI5250Field field) {
int index = getFields().indexOf(field);
if (++index < getFields().size()) {
return getFields().get(index);
} else {
return getField(0);
}
}

@Override
public void setInitialCursorPos(int column, int row) {
this.initialColumn = column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public interface TerminalEmulator {

void setCursor(int row, int col);

void setScreen(Screen screen, String screenName);
void setScreenName(String screenName);

void setScreen(Screen screen);

void soundAlarm();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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.core.Screen.Segment;
import com.blazemeter.jmeter.rte.recorder.emulator.Xtn5250TerminalEmulator.ScreenField;
import java.awt.Dimension;
import java.awt.Graphics;
Expand Down Expand Up @@ -187,7 +188,7 @@ public void setSupportedAttentionKeys(Set<AttentionKey> supportedAttentionKeys)
this.supportedAttentionKeys = supportedAttentionKeys;
}

public void setProtocolClient(T terminalClient) {
public <V extends T> void setProtocolClient(V terminalClient) {
this.terminalClient = terminalClient;
}

Expand Down Expand Up @@ -233,7 +234,7 @@ public synchronized void setScreen(Screen screen, boolean isShowCredential) {
setCrtSize(screenSize.width, screenSize.height);
clear();
removeFields();
for (Screen.Segment s : screen.getSegments()) {
for (Segment s : screen.getSegments()) {
int row = s.getStartPosition().getRow() - 1;
int column = s.getStartPosition().getColumn() - 1;
if (s.isEditable()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,16 @@ public String getScreen() {
}

@Override
public void setScreen(Screen screen, String screenName) {
public void setScreen(Screen screen) {
/*
setScreen delegated to XI5250CrtBase in order to proper synchronize setScreen,
paintComponent and processKeyEvent methods.
*/
xi5250Crt.setScreen(screen, shownCredentials);
}

@Override
public void setScreenName(String screenName) {
sampleNameField.setText(screenName);
}

Expand Down
Loading

0 comments on commit 9f50d05

Please sign in to comment.