/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.remoteaccess.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import de.rcenvironment.core.utils.common.OSFamily;
import de.rcenvironment.core.utils.common.TempFileService;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
import de.rcenvironment.core.utils.executor.testutils.IntegrationTestExecutorUtils;
import de.rcenvironment.core.utils.executor.testutils.IntegrationTestExecutorUtils.ExecutionResult;
import de.rcenvironment.core.utils.testing.ParameterizedTestUtils;
import de.rcenvironment.core.utils.testing.TestParametersProvider;
/**
* Integration tests for the standalone Remote Access client. Requires a running RCE instance to test against, and the location of the
* standalone client must be configured.
*
* @author Robert Mischke
*/
public class RemoteAccessStandaloneClientParametrizedTests {
private static final String TEST_TOOL_1_VERSION = "1.0";
private static final String TEST_TOOL_1_ID = "testTool";
private static final String TEST_TOOL_2_VERSION = "1 .,_-+()";
private static final String TEST_TOOL_2_ID = "tool .,_-+() valid name test";
private static final String DEFAULT_TESTFILE_CONTENT = "testContent";
private static final String DEFAULT_TEST_FILENAME = "test.txt";
private static final String TOOL_LIST_COLUMN_SEPARATOR = " / ";
private static final String EXPECTED_PROTOCOL_VERSION = "7.0.0-pre1";
private static final String PW_TEST_USER_ACCOUNT = "pwtest";
private static final String PW_TEST_PASSWORD = "the_pw";
// use "-Drce.network.overrideNodeId=12561256125612561256125612561256" on the test server to set this id
private static final String TEST_SERVER_NODE_ID = "12561256125612561256125612561256";
private static final String DOUBLE_QUOTE = "\"";
private static final String QUOTED_EMPTY_STRING = DOUBLE_QUOTE + DOUBLE_QUOTE;
private static final String OPERATION_ID_RUN_TOOL = "run-tool";
private static final String OPERATION_ID_LIST_TOOLS = "list-tools";
private static final String OPERATION_ID_PROTOCOL_VERSION = "protocol-version";
private static final String HOST_PARAMETER = "-h";
private static final String PORT_PARAMETER = "-p";
private static final String USER_PARAMETER = "-u";
private static final String PASSWORD_FILE_PARAMETER = "-f";
private static final String INPUT_DIR_PARAMETER = "-I";
private static final String OUTPUT_DIR_PARAMETER = "-O";
private static final String TOOL_NODE_ID_PARAMETER = "-n";
private static final String AUTHENTICATION_FAILURE_OUTPUT_TEXT_PART = "Authentication failure";
private static final String UNEXPECTED_LINE_COUNT_ON_STD_ERR = "Unexpected line count on StdErr";
private static final String UNEXPECTED_LINE_COUNT_ON_STD_OUT = "Unexpected line count on StdOut";
private static final String UNEXPECTED_EXIT_CODE = "Unexpected exit code";
private static final String TEST_FAILED_COMPLETE_OUTPUT_FOR = "Test failure: Printing complete non-debug output for ";
private static final String STDERR_LOG_PREFIX = "StdErr: ";
private static final String STDOUT_LOG_PREFIX = "StdOut: ";
private File standaloneDirectory;
private File standaloneExecutable;
private TestParametersProvider testParameters;
private IntegrationTestExecutorUtils executorUtils;
private List<String> filteredStdout;
private List<String> filteredStderr;
private String testIP;
private String testHostName;
private String testPort;
private final Log log = LogFactory.getLog(getClass());
private File inputDir;
private String inputDirPath;
private File outputDir;
private String outputDirPath;
private final TempFileService tempFileService;
public RemoteAccessStandaloneClientParametrizedTests() {
tempFileService = TempFileServiceAccess.getInstance();
}
/**
* Once-for-all-tests setup.
*/
@BeforeClass
public static void initOnce() {
TempFileServiceAccess.setupUnitTestEnvironment();
}
/**
* Common setup.
*
* @throws IOException on setup exceptions, e.g. invalid client path
*/
@Before
public void setUp() throws IOException {
// read test parameters
testParameters = new ParameterizedTestUtils().readDefaultPropertiesFile(getClass());
standaloneDirectory = testParameters.getExistingDir("remoteaccess.standalone.location");
testIP = testParameters.getNonEmptyString("testserver.ip");
testHostName = testParameters.getNonEmptyString("testserver.hostname");
testPort = testParameters.getNonEmptyString("testserver.port");
// validate
Assert.assertTrue("Invalid path", standaloneDirectory.isDirectory());
if (OSFamily.isWindows()) {
standaloneExecutable = new File(standaloneDirectory, "rce-remote.exe");
} else {
standaloneExecutable = new File(standaloneDirectory, "rce-remote");
}
Assert.assertTrue("Executable file not found", standaloneExecutable.isFile());
Assert.assertTrue("'Executable' file not actually executable", standaloneExecutable.canExecute());
executorUtils = new IntegrationTestExecutorUtils(standaloneDirectory);
// prevent accidental reuse
filteredStdout = null;
filteredStderr = null;
}
/**
* Common teardown.
*/
@After
public void tearDown() {
// visual separation
log.debug("--- End of test case --------------------------------------------------------------------");
}
/**
* Tests handling of an unknown command.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testNoConnectionAttemptOnUnknownCommand() throws Exception {
String unknownCommand = "unknownCmd";
String command = buildCommand(new String[] { unknownCommand });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 2, 0, 1);
String errorMessage = filteredStderr.get(0);
assertTrue(errorMessage.startsWith("Unknown operation parameter"));
assertTrue(errorMessage.contains(unknownCommand));
assertFalse(errorMessage.contains("Error connecting to"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests the protocol version query, using a server IP.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testVersionQueryWithIP() throws Exception {
String command = buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, OPERATION_ID_PROTOCOL_VERSION });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 0, 1, 0);
assertEquals(EXPECTED_PROTOCOL_VERSION, filteredStdout.get(0));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests the protocol version query, using a server host name.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testVersionQueryWithHostName() throws Exception {
String command =
buildCommand(new String[] { HOST_PARAMETER, testHostName, PORT_PARAMETER, testPort, OPERATION_ID_PROTOCOL_VERSION });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 0, 1, 0);
assertEquals(EXPECTED_PROTOCOL_VERSION, filteredStdout.get(0));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests handling of a non-existing host name.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testInvalidHost() throws Exception {
String command =
buildCommand(new String[] { HOST_PARAMETER, "invalid.example.org", PORT_PARAMETER, testPort, OPERATION_ID_PROTOCOL_VERSION });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 1, 0, 1);
assertTrue(filteredStderr.get(0).contains("Error connecting to invalid.example.org:" + testPort));
assertTrue(filteredStderr.get(0).contains("Failed to resolve hostname invalid.example.org"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests that the "password test" account does not work without specifying a password file.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testDefaultPasswordFailure() throws Exception {
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, USER_PARAMETER, PW_TEST_USER_ACCOUNT,
OPERATION_ID_PROTOCOL_VERSION });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 1, 0, 1);
assertTrue("Expected 'Authentication failure' message",
filteredStderr.get(0).contains(AUTHENTICATION_FAILURE_OUTPUT_TEXT_PART));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests handling of a non-existing password file.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testNonExistingPasswordFile() throws Exception {
File bogusPwFile = new File(standaloneDirectory, "bogusPwFile.txt").getAbsoluteFile();
assertFalse(bogusPwFile.exists());
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, USER_PARAMETER, PW_TEST_USER_ACCOUNT,
PASSWORD_FILE_PARAMETER, DOUBLE_QUOTE + bogusPwFile.getAbsolutePath() + DOUBLE_QUOTE, OPERATION_ID_PROTOCOL_VERSION });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 2, 0, 1);
assertEquals("The specified password file does not exist", filteredStderr.get(0));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests handling of an existing password file containing a wrong password.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testExistingWrongPasswordFile() throws Exception {
runPasswordFileScenario("wrongPW", 1);
}
/**
* Tests handling of an existing password file exactly containing the correct password.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testExistingCorrectPasswordFileWithNoTrailingLineBreakChars() throws Exception {
runPasswordFileScenario(PW_TEST_PASSWORD, 0);
}
/**
* Tests handling of an existing password file containing the correct password, followed by a newline and a line feed (\n, 0x0A).
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testExistingCorrectPasswordFileWithTrailingLF() throws Exception {
runPasswordFileScenario(PW_TEST_PASSWORD + "\n", 0);
}
/**
* Tests handling of an existing password file containing the correct password, followed by a newline and a line feed (\r\n, 0x0D0A).
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testExistingCorrectPasswordFileWithTrailingCRLFOnWindows() throws Exception {
// note: currently, \r\n is *not* expected to work on Linux: \r is taken as part of the first line, as Linux line endings
// are always \n. maybe add a specific safety check for this (check for last character == '\r') and warn the user?
if (OSFamily.isLinux()) {
log.debug("Skipping Windows-specific test on Linux");
return;
}
runPasswordFileScenario(PW_TEST_PASSWORD + "\r\n", 0);
}
/**
* Tests handling of an existing password file containing the correct password, followed by a newline and a line feed (\n, 0x0A).
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testExistingCorrectPasswordFileWithExtraContentAfterTrailingLF() throws Exception {
runPasswordFileScenario(PW_TEST_PASSWORD + "\ndummyContent", 2);
}
/**
* Tests handling of an existing password file containing the correct password, followed by a newline and a line feed (\r\n, 0x0D0A).
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testExistingCorrectPasswordFileWithExtraContentAfterTrailingCRLF() throws Exception {
runPasswordFileScenario(PW_TEST_PASSWORD + "\r\ndummyContent", 2);
}
private void runPasswordFileScenario(String passwordFileContent, int expectedExitCode) throws IOException, InterruptedException,
AssertionError {
// TODO extract common code
File pwFile = tempFileService.createTempFileFromPattern("pwfile-*.tmp");
// TODO add an additional test for newline/content after the password
FileUtils.writeStringToFile(pwFile, passwordFileContent);
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, USER_PARAMETER, PW_TEST_USER_ACCOUNT,
PASSWORD_FILE_PARAMETER, DOUBLE_QUOTE + pwFile.getAbsolutePath() + DOUBLE_QUOTE, OPERATION_ID_PROTOCOL_VERSION });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
if (expectedExitCode == 0) {
parseAndValidateExecutionResult(result, 0, 1, 0);
// check normal behavior; any operation would do here
assertEquals(EXPECTED_PROTOCOL_VERSION, filteredStdout.get(0));
} else {
parseAndValidateExecutionResult(result, expectedExitCode, 0, 1);
if (expectedExitCode == 1) {
assertTrue("Expected 'Authentication failure' message",
filteredStderr.get(0).contains(AUTHENTICATION_FAILURE_OUTPUT_TEXT_PART));
} else {
assertFalse("Unexpected 'Authentication failure' message",
filteredStderr.get(0).contains(AUTHENTICATION_FAILURE_OUTPUT_TEXT_PART));
}
}
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests fetching a list of available tools, and checks that exactly the configured test tool is available.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void listToolsAndFindExpectedTestTools() throws Exception {
String command = buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, OPERATION_ID_LIST_TOOLS });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 0, 2, 0);
assertTrue(filteredStdout.get(0).startsWith(
TEST_TOOL_1_ID + TOOL_LIST_COLUMN_SEPARATOR + TEST_TOOL_1_VERSION + TOOL_LIST_COLUMN_SEPARATOR
+ TEST_SERVER_NODE_ID + TOOL_LIST_COLUMN_SEPARATOR));
assertTrue(filteredStdout.get(1).startsWith(
TEST_TOOL_2_ID + TOOL_LIST_COLUMN_SEPARATOR + TEST_TOOL_2_VERSION + TOOL_LIST_COLUMN_SEPARATOR
+ TEST_SERVER_NODE_ID + TOOL_LIST_COLUMN_SEPARATOR));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests a valid case of remote tool execution; node id is left empty, so the only available installation should be used.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithValidDirectoriesAndNoNodeId() throws Exception {
setupTempInputDir();
setupTempOutputDir();
String cmdParameters = "123 - 546";
generateInputFile(DEFAULT_TEST_FILENAME, DEFAULT_TESTFILE_CONTENT);
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, TEST_TOOL_1_ID, TEST_TOOL_1_VERSION,
DOUBLE_QUOTE + cmdParameters + DOUBLE_QUOTE });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutLines = 15;
parseAndValidateExecutionResult(result, 0, expectedStdoutLines, 0);
validateOutputFile("params.txt", DOUBLE_QUOTE + cmdParameters + DOUBLE_QUOTE, true);
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests a remote tool execution that is valid except for an unsafe character in the parameter string.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testValidRunToolCommandWithInsecureParameterString() throws Exception {
setupTempInputDir();
setupTempOutputDir();
// TODO once this test has a better parameterization setup, add more test cases for this
String cmdParameters = "123 - 546`x";
generateInputFile(DEFAULT_TEST_FILENAME, DEFAULT_TESTFILE_CONTENT);
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, TEST_TOOL_1_ID, TEST_TOOL_1_VERSION,
DOUBLE_QUOTE + cmdParameters + DOUBLE_QUOTE });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutLines = 5; // side effect of no pre-validation in client, so execution is attempted
parseAndValidateExecutionResult(result, 1, expectedStdoutLines, 1);
// check for correct error message
assertTrue(filteredStderr.get(0).contains("forbidden character"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests with the second test tool (which contains all allowed characters in its id and version).
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithTool2AndValidDirectoriesAndNoNodeId() throws Exception {
setupTempInputDir();
setupTempOutputDir();
String cmdParameters = "123 546";
generateInputFile(DEFAULT_TEST_FILENAME, DEFAULT_TESTFILE_CONTENT);
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, DOUBLE_QUOTE + TEST_TOOL_2_ID + DOUBLE_QUOTE,
DOUBLE_QUOTE + TEST_TOOL_2_VERSION + DOUBLE_QUOTE, DOUBLE_QUOTE + cmdParameters + DOUBLE_QUOTE });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutLines = 15;
parseAndValidateExecutionResult(result, 0, expectedStdoutLines, 0);
validateOutputFile("params.txt", DOUBLE_QUOTE + cmdParameters + DOUBLE_QUOTE, true);
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests a valid case of remote tool execution; node id is left empty, so the only available installation should be used.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithValidDirectoriesAndCorrectNodeId() throws Exception {
setupTempInputDir();
setupTempOutputDir();
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, TOOL_NODE_ID_PARAMETER, TEST_SERVER_NODE_ID, TEST_TOOL_1_ID,
TEST_TOOL_1_VERSION, QUOTED_EMPTY_STRING });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutLines = 15;
parseAndValidateExecutionResult(result, 0, expectedStdoutLines, 0);
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests a valid case of remote tool execution; node id is left empty, so the only available installation should be used.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithValidDirectoriesAndWrongNodeId() throws Exception {
setupTempInputDir();
setupTempOutputDir();
String wrongNodeId = "98989898989898989898989898989898";
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, TOOL_NODE_ID_PARAMETER, wrongNodeId,
TEST_TOOL_1_ID, TEST_TOOL_1_VERSION, QUOTED_EMPTY_STRING });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutLines = 5;
parseAndValidateExecutionResult(result, 1, expectedStdoutLines, 1);
String errorMessage = filteredStderr.get(0);
assertTrue(errorMessage.contains("No matching tool"));
assertTrue("Error message should contain the tool id", errorMessage.contains(TEST_TOOL_1_ID));
assertTrue("Error message should contain the tool version", errorMessage.contains(TEST_TOOL_1_VERSION));
assertTrue("Error message should contain the wrong node id",
errorMessage.contains("running on a node with id '" + wrongNodeId + "'"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests the behavior if no input directory is given, and the default does not exist.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithMissingInputDirectory() throws Exception {
assertNoDefaultInputOutputDirectoriesExist();
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, OPERATION_ID_RUN_TOOL, TEST_TOOL_1_ID,
TEST_TOOL_1_VERSION, QUOTED_EMPTY_STRING });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 1, 0, 1);
String errorMessage = filteredStderr.get(0);
assertTrue("Expected input directory error message", errorMessage.contains("Input (upload) directory"));
assertTrue("Expected input directory error message", errorMessage.contains("does not exist"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests the behavior if no output directory is given, and the default does not exist.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithMissingOutputDirectory() throws Exception {
assertNoDefaultInputOutputDirectoriesExist();
setupTempInputDir();
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OPERATION_ID_RUN_TOOL, TEST_TOOL_1_ID, TEST_TOOL_1_VERSION, QUOTED_EMPTY_STRING });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 1, 0, 1);
String errorMessage = filteredStderr.get(0);
assertTrue("Expected output directory error message", errorMessage.contains("Output (download) directory"));
assertTrue("Expected output directory error message", errorMessage.contains("does not exist"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests a valid case of remote tool execution.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithDisabledUploadAndDownload() throws Exception {
generateInputFile(DEFAULT_TEST_FILENAME, "test content");
String toolParameters = "test parameters";
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, QUOTED_EMPTY_STRING,
OUTPUT_DIR_PARAMETER, QUOTED_EMPTY_STRING, OPERATION_ID_RUN_TOOL, TEST_TOOL_1_ID, TEST_TOOL_1_VERSION, DOUBLE_QUOTE
+ toolParameters + DOUBLE_QUOTE });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutLines = 13;
parseAndValidateExecutionResult(result, 0, expectedStdoutLines, 0);
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Triggers execution of a non-existing tool, and checks the error handling.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithInvalidToolId() throws Exception {
String bogusToolId = "bogusId";
String bogusToolVersion = TEST_TOOL_1_VERSION; // intentionally one of an existing tool
setupTempInputDir();
setupTempOutputDir();
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, bogusToolId, bogusToolVersion, "xyz" });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStdoutSize = 5;
parseAndValidateExecutionResult(result, 1, expectedStdoutSize, 1);
String errorMessage = filteredStderr.get(0);
assertTrue("Error message should contain the invalid tool's id", errorMessage.contains(bogusToolId));
assertTrue("Error message should contain the invalid tool's version", errorMessage.contains(bogusToolVersion));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Tests validation of the tool id with an invalid character.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithInvalidCharacterInToolId() throws Exception {
String bogusToolId = "bogus#Id";
String bogusToolVersion = TEST_TOOL_1_VERSION; // intentionally one of an existing tool
setupTempInputDir();
setupTempOutputDir();
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, bogusToolId, bogusToolVersion,
DOUBLE_QUOTE + "xyz 2 #%§" + DOUBLE_QUOTE });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
final int expectedStderrSize = 1;
parseAndValidateExecutionResult(result, 1, 0, expectedStderrSize);
String errorMessage = filteredStderr.get(0);
assertTrue("Error message should contain 'tool id'", errorMessage.contains("tool id"));
assertTrue("Error message should describe the error", errorMessage.contains("invalid character at position 6"));
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
/**
* Triggers execution of a non-existing tool with unusual characters in the tool id and version.
*
* @throws Exception on unhandled test exceptions
*/
@Test
public void testRunToolCommandWithMalformedToolId() throws Exception {
// TODO also test double quotes, \0, line breaks, ... -> check how to properly pass to tool via command line first
String bogusToolId = "äöüÄÖÜß`´' endOfToolId";
String bogusToolVersion = "äöüÄÖÜß`´' endOfToolVersion";
setupTempInputDir();
setupTempOutputDir();
String command =
buildCommand(new String[] { HOST_PARAMETER, testIP, PORT_PARAMETER, testPort, INPUT_DIR_PARAMETER, inputDirPath,
OUTPUT_DIR_PARAMETER, outputDirPath, OPERATION_ID_RUN_TOOL, bogusToolId, bogusToolVersion, "xyz" });
ExecutionResult result = executorUtils.executeAndWait(command);
try {
parseAndValidateExecutionResult(result, 2, 0, 1);
// String errorMessage = filteredStderr.get(0);
// TODO add more conditions for error message
} catch (AssertionError e) {
logFullOutputOnTestFailureAndRethrow(filteredStdout, filteredStderr, e);
}
}
private void setupTempInputDir() throws IOException {
assertNull(inputDir);
inputDir = tempFileService.createManagedTempDir("input");
inputDirPath = DOUBLE_QUOTE + inputDir.getAbsolutePath() + DOUBLE_QUOTE; // for safe(r) CLI execution
}
private void setupTempOutputDir() throws IOException {
assertNull(outputDir);
outputDir = tempFileService.createManagedTempDir("output");
outputDirPath = DOUBLE_QUOTE + outputDir.getAbsolutePath() + DOUBLE_QUOTE; // for safe(r) CLI execution
}
private void generateInputFile(String filename, String content) throws IOException {
File file = new File(inputDir, filename);
FileUtils.writeStringToFile(file, content);
}
private void validateOutputFile(String filename, String content, boolean ignoreOuterWhitespace) throws IOException {
File file = new File(outputDir, filename);
assertTrue("Expected output file " + filename + " does not exist", file.isFile());
assertEquals("Content of output file " + filename + " does not match with expectation", content.trim(),
FileUtils.readFileToString(file).trim());
}
private void assertNoDefaultInputOutputDirectoriesExist() {
File defaultInputDir = new File(standaloneDirectory, "input");
assertFalse("There is a default 'input' directory in the client directory; it must be removed for reliable testing",
defaultInputDir.exists());
File defaultOutputDir = new File(standaloneDirectory, "output");
assertFalse("There is a default 'output' directory in the client directory; it must be removed for reliable testing",
defaultOutputDir.exists());
}
private String buildCommand(String[] cmdParts) {
StringBuilder buffer = new StringBuilder();
if (OSFamily.isLinux()) {
buffer.append("export LD_LIBRARY_PATH=. && ");
}
buffer.append(standaloneExecutable.getAbsolutePath());
for (String cmdPart : cmdParts) {
// TODO add escaping/quoting
buffer.append(' ');
buffer.append(cmdPart);
}
String command = buffer.toString();
log.debug("Command line: " + command);
return command;
}
private List<String> logOutputAndRemoveDebugLines(List<String> original, String prefix) {
List<String> filtered = new ArrayList<>(original.size());
for (String line : original) {
log.debug(prefix + line);
if (!line.startsWith("[DEBUG] ")) {
filtered.add(line);
}
}
return filtered;
}
private void parseAndValidateExecutionResult(ExecutionResult result, int exitCode, int stdOutSize, int stdErrSize)
throws AssertionError {
filteredStdout = logOutputAndRemoveDebugLines(result.stdoutLines, STDOUT_LOG_PREFIX);
filteredStderr = logOutputAndRemoveDebugLines(result.stderrLines, STDERR_LOG_PREFIX);
assertEquals(UNEXPECTED_EXIT_CODE, exitCode, result.exitCode);
assertEquals(UNEXPECTED_LINE_COUNT_ON_STD_OUT, stdOutSize, filteredStdout.size());
assertEquals(UNEXPECTED_LINE_COUNT_ON_STD_ERR, stdErrSize, filteredStderr.size());
}
private void logFullOutputOnTestFailureAndRethrow(List<String> stdout, List<String> stderr, AssertionError e)
throws AssertionError {
// log full output on test failure
log.error(TEST_FAILED_COMPLETE_OUTPUT_FOR + STDOUT_LOG_PREFIX + stdout);
log.error(TEST_FAILED_COMPLETE_OUTPUT_FOR + STDERR_LOG_PREFIX + stderr);
throw e;
}
}