Moved MockTerminal and created a base test case for cli commands.

This commit is contained in:
Ryan Ernst 2016-03-07 12:42:15 -08:00
parent 7a49cd1287
commit 45b5ab24fe
13 changed files with 75 additions and 171 deletions

View File

@ -49,14 +49,14 @@ public abstract class Command {
}
/** Parses options for this command from args and executes it. */
public final int main(String[] args, Terminal terminal) throws Exception {
protected final int main(String[] args, Terminal terminal) throws Exception {
final OptionSet options;
try {
options = parser.parse(args);
} catch (OptionException e) {
printHelp(terminal);
terminal.println("ERROR: " + e.getMessage());
terminal.println(Terminal.Verbosity.SILENT, "ERROR: " + e.getMessage());
return ExitCodes.USAGE;
}
@ -69,7 +69,7 @@ public abstract class Command {
if (options.has(verboseOption)) {
// mutually exclusive, we can remove this with jopt-simple 5.0, which natively supports it
printHelp(terminal);
terminal.println("ERROR: Cannot specify -s and -v together");
terminal.println(Terminal.Verbosity.SILENT, "ERROR: Cannot specify -s and -v together");
return ExitCodes.USAGE;
}
terminal.setVerbosity(Terminal.Verbosity.SILENT);

View File

@ -1,138 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.cli;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A helper command that checks if configured paths have been changed when running a CLI command.
* It is only executed in case of specified paths by the command and if the paths underlying filesystem
* supports posix permissions.
*
* If this is the case, a warn message is issued whenever an owner, a group or the file permissions is changed by
* the command being executed and not configured back to its prior state, which should be the task of the command
* being executed.
*
*/
public abstract class CheckFileCommand extends CliTool.Command {
public CheckFileCommand(Terminal terminal) {
super(terminal);
}
/**
* abstract method, which should implement the same logic as CliTool.Command.execute(), but is wrapped
*/
public abstract CliTool.ExitStatus doExecute(Settings settings, Environment env) throws Exception;
/**
* Returns the array of paths, that should be checked if the permissions, user or groups have changed
* before and after execution of the command
*
*/
protected abstract Path[] pathsForPermissionsCheck(Settings settings, Environment env) throws Exception;
@Override
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
Path[] paths = pathsForPermissionsCheck(settings, env);
if (paths == null || paths.length == 0) {
return doExecute(settings, env);
}
Map<Path, Set<PosixFilePermission>> permissions = new HashMap<>(paths.length);
Map<Path, String> owners = new HashMap<>(paths.length);
Map<Path, String> groups = new HashMap<>(paths.length);
if (paths != null && paths.length > 0) {
for (Path path : paths) {
try {
boolean supportsPosixPermissions = Environment.getFileStore(path).supportsFileAttributeView(PosixFileAttributeView.class);
if (supportsPosixPermissions) {
PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class);
permissions.put(path, attributes.permissions());
owners.put(path, attributes.owner().getName());
groups.put(path, attributes.group().getName());
}
} catch (IOException e) {
// silently swallow if not supported, no need to log things
}
}
}
CliTool.ExitStatus status = doExecute(settings, env);
// check if permissions differ
for (Map.Entry<Path, Set<PosixFilePermission>> entry : permissions.entrySet()) {
if (!Files.exists(entry.getKey())) {
continue;
}
Set<PosixFilePermission> permissionsBeforeWrite = entry.getValue();
Set<PosixFilePermission> permissionsAfterWrite = Files.getPosixFilePermissions(entry.getKey());
if (!permissionsBeforeWrite.equals(permissionsAfterWrite)) {
terminal.println(Terminal.Verbosity.SILENT, "WARNING: The file permissions of [" + entry.getKey() + "] have changed "
+ "from [" + PosixFilePermissions.toString(permissionsBeforeWrite) + "] "
+ "to [" + PosixFilePermissions.toString(permissionsAfterWrite) + "]");
terminal.println(Terminal.Verbosity.SILENT, "Please ensure that the user account running Elasticsearch has read access to this file!");
}
}
// check if owner differs
for (Map.Entry<Path, String> entry : owners.entrySet()) {
if (!Files.exists(entry.getKey())) {
continue;
}
String ownerBeforeWrite = entry.getValue();
String ownerAfterWrite = Files.getOwner(entry.getKey()).getName();
if (!ownerAfterWrite.equals(ownerBeforeWrite)) {
terminal.println(Terminal.Verbosity.SILENT, "WARNING: Owner of file [" + entry.getKey() + "] used to be [" + ownerBeforeWrite + "], but now is [" + ownerAfterWrite + "]");
}
}
// check if group differs
for (Map.Entry<Path, String> entry : groups.entrySet()) {
if (!Files.exists(entry.getKey())) {
continue;
}
String groupBeforeWrite = entry.getValue();
String groupAfterWrite = Files.readAttributes(entry.getKey(), PosixFileAttributes.class).group().getName();
if (!groupAfterWrite.equals(groupBeforeWrite)) {
terminal.println(Terminal.Verbosity.SILENT, "WARNING: Group of file [" + entry.getKey() + "] used to be [" + groupBeforeWrite + "], but now is [" + groupAfterWrite + "]");
}
}
return status;
}
}

View File

@ -19,7 +19,10 @@
package org.elasticsearch.common.cli;
public class TerminalTests extends CliToolTestCase {
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.test.ESTestCase;
public class TerminalTests extends ESTestCase {
public void testVerbosity() throws Exception {
MockTerminal terminal = new MockTerminal();
terminal.setVerbosity(Terminal.Verbosity.SILENT);
@ -48,7 +51,7 @@ public class TerminalTests extends CliToolTestCase {
logTerminal.println(verbosity, text);
String output = logTerminal.getOutput();
assertTrue(output, output.contains(text));
logTerminal.resetOutput();
logTerminal.reset();
}
private void assertNotPrinted(MockTerminal logTerminal, Terminal.Verbosity verbosity, String text) throws Exception {

View File

@ -27,7 +27,7 @@ import java.util.Arrays;
import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;

View File

@ -24,7 +24,7 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;

View File

@ -20,10 +20,8 @@
package org.elasticsearch.plugins;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.cli.MockTerminal;
import static org.elasticsearch.common.cli.CliTool.ExitStatus.OK_AND_EXIT;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;

View File

@ -25,7 +25,7 @@ import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.CliTool.ExitStatus;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.monitor.jvm.JvmInfo;
import org.junit.After;
@ -89,7 +89,7 @@ public class BootstrapCliParserTests extends CliToolTestCase {
assertTrue(output, output.contains(Build.CURRENT.date()));
assertTrue(output, output.contains(JvmInfo.jvmInfo().version()));
terminal.resetOutput();
terminal.reset();
parser = new BootstrapCLIParser(terminal);
status = parser.execute(args("start --version"));
assertStatus(status, OK_AND_EXIT);
@ -177,7 +177,7 @@ public class BootstrapCliParserTests extends CliToolTestCase {
String output = terminal.getOutput();
assertTrue(output, output.contains("Parameter [network.host] needs value"));
terminal.resetOutput();
terminal.reset();
status = parser.execute(args("start --network.host --foo"));
assertStatus(status, USAGE);
output = terminal.getOutput();
@ -194,7 +194,7 @@ public class BootstrapCliParserTests extends CliToolTestCase {
assertTrue(output, output.contains("Unrecognized option: --unknown-param"));
// single dash in extra params
terminal.resetOutput();
terminal.reset();
parser = new BootstrapCLIParser(terminal);
status = parser.execute(args("start -network.host 127.0.0.1"));
assertStatus(status, USAGE);
@ -228,7 +228,7 @@ public class BootstrapCliParserTests extends CliToolTestCase {
tuples.add(new Tuple<>("-h", "elasticsearch.help"));
for (Tuple<String, String> tuple : tuples) {
terminal.resetOutput();
terminal.reset();
BootstrapCLIParser parser = new BootstrapCLIParser(terminal);
ExitStatus status = parser.execute(args(tuple.v1()));
assertStatus(status, OK_AND_EXIT);

View File

@ -36,7 +36,6 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -45,11 +44,7 @@ import java.util.zip.ZipOutputStream;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.Version;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;

View File

@ -22,15 +22,10 @@ package org.elasticsearch.plugins;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;

View File

@ -26,9 +26,7 @@ import java.nio.file.Path;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.MockTerminal;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;

View File

@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.cli;
import joptsimple.OptionSet;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
/**
* A base test case for cli tools.
*/
public abstract class CommandTestCase extends ESTestCase {
protected final MockTerminal terminal = new MockTerminal();
@Before
public void resetTerminal() {
terminal.reset();
terminal.setVerbosity(Terminal.Verbosity.NORMAL);
}
protected abstract Command newCommand();
public String execute(String... args) throws Exception {
Command command = newCommand();
OptionSet options = command.parser.parse(args);
command.execute(terminal, options);
return terminal.getOutput();
}
}

View File

@ -17,7 +17,7 @@
* under the License.
*/
package org.elasticsearch.common.cli;
package org.elasticsearch.cli;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
@ -27,6 +27,8 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
import org.elasticsearch.common.cli.Terminal;
/**
* A terminal for tests which captures all output, and
* can be plugged with fake input.
@ -78,8 +80,10 @@ public class MockTerminal extends Terminal {
return buffer.toString("UTF-8");
}
/** Wipes the output. */
public void resetOutput() {
/** Wipes the input and output. */
public void reset() {
buffer.reset();
textInput.clear();
secretInput.clear();
}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.common.cli;
import java.io.IOException;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.test.ESTestCase;