Skip to content

Repository files navigation

Build StatusCoverage Status

Yet Another Expect for Java

Overview

ExpectIt - is yet another pure Java 1.6+ implementation of the Expect tool. It is designed to be simple, easy to use and extensible. Written from scratch. Here are the features:

  • Fluent-style API.
  • No third-party dependencies.
  • NIO based implementation using pipes and non-blocking API.
  • Extensible matcher framework. Support regular expressions and group operations.
  • Support multiple input streams.
  • Support 'interact' loop.
  • Extensible filter framework to modify input, for example, to remove non-printable ANSI terminal characters.
  • Custom Expect Ant Task.
  • Tested on Andriod.
  • Apache License.

The ExpectIt project is a modern alternative to other popular 'Expect for Java' implementations, such as:

I believe that none of the projects above has all the features that ExpectIt has. So if you are looking for a Java expect library please give ExpectIt a try.

The API javadoc documentation is available here.

Quick start

The library is available on the Maven central. Add the following Maven dependency to your project:

 <dependency>
<groupId>net.sf.expectit</groupId>
<artifactId>expectit-core</artifactId>
<version>0.9.0</version>
</dependency>

You can also download the expectit-core.jar file from the release project page at sourceforge.net and add it to your classpath.

To begin with you need to construct an instance of net.sf.expectit.Expect and set the input and output streams as follows:

// the stream from where you read your input dataInputStreaminputStream = ...;
// the stream to where you send commandsOutputStreamoutputStream = ...;
Expectexpect = newExpectBuilder()
.withInputs(inputStream)
.withOutput(outputStream)
.build();
expect.sendLine("command").expect(contains("string"));
Resultresult = expect.expect(regexp("(.*)--?--(.*)"));
// accessing the matching groupStringgroup = result.group(2);

Note that you may need to add static import of the matcher factory methods in your code.

How it works

Once an Expect object is created the library starts background threads for every input stream. The threads read bytes from the streams and copy them into NIO pipes. The pipes are configured to use non-blocking source channel.

The expect object holds a String buffer for each input. The user calls one of the expect methods to wait until the given matcher object matches the corresponding buffer contents. If the input buffer doesn't satisfy the matcher criteria, then the method blocks for a configurable timeout of milliseconds until new data is available on the input stream NIO pipe.

The result object indicates whether the match operation was successful or not. It holds the context of the match. It implements the java.util.regexp.MatchResult interface which provides access to the result of regular expression matching results. If the match was successful, then the corresponding input buffer is update, all characters before the match including the matching string are removed. The next match is performed for the updated buffer.

Thread safety notes

The send methods are generally thread safe as long as the underlying output streams are. In other words it is safe to send data from one thread and expect the results in another.

The expect methods are not thread safe since they mutate the state of the expect buffers. The expect operaiton must not be performed concurrently.

Interacting with OS process

Here is an example of interacting with a spawn process:

Processprocess = Runtime.getRuntime().exec("/bin/sh");
Expectexpect = newExpectBuilder()
.withInputs(process.getInputStream())
.withOutput(process.getOutputStream())
.withTimeout(1, TimeUnit.SECONDS)
.withExceptionOnFailure()
.build();
// try-with-resources is omitted for simplicityexpect.sendLine("ls -lh");
// capture the totalStringtotal = expect.expect(regexp("^total (.*)")).group(1);
System.out.println("Size: " + total);
// capture file listStringlist = expect.expect(regexp("\n$")).getBefore();
// print the resultSystem.out.println("List: " + list);
expect.sendLine("exit");
// expect the process to finishexpect.expect(eof());
// finally is omittedprocess.waitFor();
expect.close();

Interacting with SSH server

Here is an example on how to talk to a public SSH service on http://sdf.org using the JSch library. Note: you will to add the jsch library to your project classpath.

JSchjSch = newJSch();
Sessionsession = jSch.getSession("new", "sdf.org");
Propertiesconfig = newProperties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
Channelchannel = session.openChannel("shell");
channel.connect();
Expectexpect = newExpectBuilder()
.withOutput(channel.getOutputStream())
.withInputs(channel.getInputStream(), channel.getExtInputStream())
.withEchoOutput(System.out)
.withEchoInput(System.err)
// .withInputFilters(removeColors(), removeNonPrintable())
.withExceptionOnFailure()
.build();
try {
expect.expect(contains("[RETURN]"));
expect.sendLine();
StringipAddress = expect.expect(regexp("Trying (.*)\\.\\.\\.")).group(1);
System.out.println("Captured IP: " + ipAddress);
expect.expect(contains("login:"));
expect.sendLine("new");
expect.expect(contains("(Y/N)"));
expect.send("N");
expect.expect(regexp(": $"));
} finally {
expect.close();
channel.disconnect();
session.disconnect();
}

Note that SSH servers normally echo the received commands. The echo can be disabled by sending the stty -echo command. This is an example of capturing the result of the pwd command when the command echo is switched off.

Using different type of matchers

In the following example you can see how to combine different matchers (assuming static import of matcher factory methods):

// match any of predicatesexpect.expect(anyOf(contains("string"), regexp("abc.*def")));
// match allexpect.expect(allOf(regexp("xyz"), regexp("abc.*def")));
// varargs method arguments are equivalent to 'allOf'expect.expect(contains("string1"), contains("string2"));
// expect to match three times in a rowexpect.expect(times(3, contains("string")));
// expect any non-empty string matchexpect.expect(anyString());
// expect to contain "a" and after that "b"expect.expect(sequence(contains("a"), contains("b")));

Filtering the input

If you want to modify or remove some characters in the input before performing expect operations you can use filters. A filter instance implements net.sf.expectit.filter.Filter interface and is applied right before the matching occurs.

Filters are defined at the time an net.sf.expectit.Expect instance is being created and they can be disabled and re-enabled while working with the Expect instance.

The library comes with the filters for removing ANSI escape terminal and non-printable characters. There are also more general replaceInString and replaceInBuffer filters used to modify the input buffer using regular expressions. Here is an example:

Expectexpect = newExpectBuilder()
.withOutput(...)
.withInputs(...)
// define the filters
.withInputFilters(
// set the filter to remove ANSI char for colors in terminalremoveColors(),
// set the filter to remove non-printable charactersremoveNonPrintable(),
// set the filter to replace a substring that matches // the regular expressionreplaceInString("a(.)c", "x$1z"))
.build();

Note that you may need to add static import of the filter factory methods in your code.

Please be careful about the order you declare filters in.
Filters are called in the order they were declared in.

For example, removeNonPrintable() removes \e (\033 or \x1B), which is the ESC character.
ANSI color sequences however uses \e as part of the color sequence.
So if you declare the removeNonPrintable() filter before the removeColors() filter, removeColors() filter will NOT work.

More examples

Questions

If you have any questions about the library please post a message to this Google group. You can also ask a question on stackoverflow with hash tag expectit.

License

Apache License, Version 2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages