blob: 139247a2f846008d529ad71eef95df494204e052 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.accumulo.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import org.apache.accumulo.harness.SharedMiniClusterBase;
import org.apache.accumulo.shell.Shell;
import org.apache.accumulo.test.categories.MiniClusterOnlyTests;
import org.apache.accumulo.test.categories.SunnyDayTests;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.impl.DumbTerminal;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Category({MiniClusterOnlyTests.class, SunnyDayTests.class})
public class ShellIT extends SharedMiniClusterBase {
@Override
protected int defaultTimeoutSeconds() {
return 30;
}
@BeforeClass
public static void setup() throws Exception {
SharedMiniClusterBase.startMiniCluster();
}
@AfterClass
public static void teardown() {
SharedMiniClusterBase.stopMiniCluster();
}
private static final Logger log = LoggerFactory.getLogger(ShellIT.class);
public static class TestOutputStream extends OutputStream {
StringBuilder sb = new StringBuilder();
@Override
public void write(int b) {
sb.append((char) (0xff & b));
}
public String get() {
return sb.toString();
}
public void clear() {
sb.setLength(0);
}
}
public static class StringInputStream extends InputStream {
private String source = "";
private int offset = 0;
@Override
public int read() {
if (offset == source.length()) {
return '\n';
} else {
return source.charAt(offset++);
}
}
public void set(String other) {
source = other;
offset = 0;
}
}
private StringInputStream input;
private TestOutputStream output;
private Shell shell;
private File config;
public LineReader reader;
public Terminal terminal;
void execExpectList(String cmd, boolean expecteGoodExit, List<String> expectedStrings)
throws IOException {
exec(cmd);
if (expecteGoodExit) {
assertGoodExit("", true);
} else {
assertBadExit("", true);
}
for (String expectedString : expectedStrings) {
assertTrue(expectedString + " was not present in " + output.get(),
output.get().contains(expectedString));
}
}
void exec(String cmd) throws IOException {
output.clear();
shell.execCommand(cmd, true, true);
}
void exec(String cmd, boolean expectGoodExit) throws IOException {
exec(cmd);
if (expectGoodExit) {
assertGoodExit("", true);
} else {
assertBadExit("", true);
}
}
void exec(String cmd, boolean expectGoodExit, String expectString) throws IOException {
exec(cmd, expectGoodExit, expectString, true);
}
void exec(String cmd, boolean expectGoodExit, String expectString, boolean stringPresent)
throws IOException {
exec(cmd);
if (expectGoodExit) {
assertGoodExit(expectString, stringPresent);
} else {
assertBadExit(expectString, stringPresent);
}
}
@Before
public void setupShell() throws IOException {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
output = new TestOutputStream();
input = new StringInputStream();
config = Files.createTempFile(null, null).toFile();
terminal = new DumbTerminal(input, output);
terminal.setSize(new Size(80, 24));
reader = LineReaderBuilder.builder().terminal(terminal).build();
shell = new Shell(reader);
shell.setLogErrorsToConsole();
shell.config("--config-file", config.toString(), "-u", "root", "-p", getRootPassword(), "-zi",
getCluster().getInstanceName(), "-zh", getCluster().getZooKeepers());
}
@After
public void teardownShell() {
if (config.exists()) {
if (!config.delete()) {
log.error("Unable to delete {}", config);
}
}
shell.shutdown();
}
void assertGoodExit(String s, boolean stringPresent) {
Shell.log.debug("{}", output.get());
assertEquals(shell.getExitCode(), 0);
if (!s.isEmpty()) {
assertEquals(s + " present in " + output.get() + " was not " + stringPresent, stringPresent,
output.get().contains(s));
}
}
void assertBadExit(String s, boolean stringPresent) {
Shell.log.debug("{}", output.get());
assertTrue(shell.getExitCode() > 0);
if (!s.isEmpty()) {
assertEquals(s + " present in " + output.get() + " was not " + stringPresent, stringPresent,
output.get().contains(s));
}
shell.resetExitCode();
}
@Test
public void aboutTest() throws IOException {
Shell.log.debug("Starting about test -----------------------------------");
exec("about", true, "Shell - Apache Accumulo Interactive Shell");
exec("about -v", true, "Current user:");
exec("about arg", false, "java.lang.IllegalArgumentException: Expected 0 arguments");
}
@Test
public void addGetSplitsTest() throws IOException {
Shell.log.debug("Starting addGetSplits test ----------------------------");
exec("addsplits arg", false, "java.lang.IllegalStateException: Not in a table context");
exec("createtable test", true);
exec("addsplits 1 \\x80", true);
exec("getsplits", true, "1\n\\x80");
exec("getsplits -m 1", true, "1");
exec("getsplits -b64", true, "MQ==\ngA==");
exec("deletetable test -f", true, "Table: [test] has been deleted");
}
@Test
public void insertDeleteScanTest() throws IOException {
Shell.log.debug("Starting insertDeleteScan test ------------------------");
exec("insert r f q v", false, "java.lang.IllegalStateException: Not in a table context");
exec("delete r f q", false, "java.lang.IllegalStateException: Not in a table context");
exec("createtable test", true);
exec("insert r f q v", true);
exec("scan", true, "r f:q []\tv");
exec("delete r f q", true);
exec("scan", true, "r f:q []\tv", false);
exec("insert \\x90 \\xa0 \\xb0 \\xc0\\xd0\\xe0\\xf0", true);
exec("scan", true, "\\x90 \\xA0:\\xB0 []\t\\xC0\\xD0");
exec("scan -f 2", true, "\\x90 \\xA0:\\xB0 []\t\\xC0\\xD0");
exec("scan -f 2", true, "\\x90 \\xA0:\\xB0 []\t\\xC0\\xD0\\xE0", false);
exec("scan -b \\x90 -e \\x90 -c \\xA0", true, "\\x90 \\xA0:\\xB0 []\t\\xC0");
exec("scan -b \\x90 -e \\x90 -c \\xA0:\\xB0", true, "\\x90 \\xA0:\\xB0 []\t\\xC0");
exec("scan -b \\x90 -be", true, "\\x90 \\xA0:\\xB0 []\t\\xC0", false);
exec("scan -e \\x90 -ee", true, "\\x90 \\xA0:\\xB0 []\t\\xC0", false);
exec("scan -b \\x90\\x00", true, "\\x90 \\xA0:\\xB0 []\t\\xC0", false);
exec("scan -e \\x8f", true, "\\x90 \\xA0:\\xB0 []\t\\xC0", false);
exec("delete \\x90 \\xa0 \\xb0", true);
exec("scan", true, "\\x90 \\xA0:\\xB0 []\t\\xC0", false);
exec("deletetable test -f", true, "Table: [test] has been deleted");
// Add tests to verify use of --table parameter
exec("createtable test2", true);
exec("notable", true);
exec("insert r f q v", false, "java.lang.IllegalStateException: Not in a table context");
exec("insert r1 f1 q1 v1 -t test2", true);
exec("insert r2 f2 q2 v2 --table test2", true);
exec("delete r1 f1 q1 -t", false,
"org.apache.commons.cli.MissingArgumentException: Missing argument for option:");
exec("delete r1 f1 q1 -t test3", false,
"org.apache.accumulo.core.client.TableNotFoundException:");
exec("scan -t test2", true, "r1 f1:q1 []\tv1\nr2 f2:q2 []\tv2");
exec("delete r1 f1 q1 -t test2", true);
exec("scan -t test2", true, "r1 f1:q1 []\tv1", false);
exec("scan -t test2", true, "r2 f2:q2 []\tv2", true);
exec("delete r2 f2 q2 --table test2", true);
exec("scan -t test2", true, "r1 f1:q1 []\tv1", false);
exec("scan -t test2", true, "r2 f2:q2 []\tv2", false);
exec("deletetable test2 -f", true, "Table: [test2] has been deleted");
}
@Test
public void insertIntoSpecifiedTableTest() throws IOException {
Shell.log.debug("Starting insertIntoSpecifiedTableTest -----------------");
// create two tables for insertion tests
exec("createtable tab1", true);
exec("createtable tab2", true);
// insert data into tab2 while in tab2 context
exec("insert row1 f q tab2", true);
// insert another with the table and t argument to verify also works
exec("insert row2 f q tab2 --table tab2", true);
exec("insert row3 f q tab2 -t tab2", true);
// leave all table contexts
exec("notable", true);
// without option cannot insert when not in a table context, also cannot add to a table
// using 'accumulo shell -e "insert ...." fron command line due to no table context being set.
exec("insert row1 f q tab1", false, "java.lang.IllegalStateException: Not in a table context");
// but using option can insert to a table with tablename option without being in a table context
exec("insert row1 f q tab1 --table tab1", true);
exec("insert row4 f q tab2 -t tab2", true);
exec("table tab2", true);
// can also insert into another table even if a different table context
exec("insert row2 f q tab1 -t tab1", true);
exec("notable", true);
// must supply a tablename if option is used
exec("insert row5 f q tab5 --table", false,
"org.apache.commons.cli.MissingArgumentException: Missing argument for option:");
exec("insert row5 f q tab5 --t", false,
"org.apache.commons.cli.AmbiguousOptionException: Ambiguous option: '--t'");
// verify expected data is in both tables
exec("scan -t tab1", true, "row1 f:q []\ttab1\nrow2 f:q []\ttab1");
exec("scan -t tab2", true,
"row1 f:q []\ttab2\nrow2 f:q []\ttab2\nrow3 f:q []\ttab2\nrow4 f:q []\ttab2");
// check that if in table context, inserting into a non-existent table does not change context
exec("createtable tab3", true);
exec("table tab3", true);
exec("insert row1 f1 q1 tab3", true);
exec("insert row2 f2 q2 tab3 --table idontexist", false,
"org.apache.accumulo.core.client.TableNotFoundException:");
exec("insert row2 f2 q2 tab3 -t idontexist", false,
"org.apache.accumulo.core.client.TableNotFoundException:");
exec("insert row3 f3 q3 tab3", true); // should be able to insert w/o changing tables
// verify expected data is in tab3
exec("scan", true, "row1 f1:q1 []\ttab3\nrow3 f3:q3 []\ttab3");
// cleanup
exec("deletetable tab1 -f", true, "Table: [tab1] has been deleted");
exec("deletetable tab2 -f", true, "Table: [tab2] has been deleted");
exec("deletetable tab3 -f", true, "Table: [tab3] has been deleted");
}
@Test
public void deleteManyTest() throws IOException {
exec("deletemany", false, "java.lang.IllegalStateException: Not in a table context");
exec("createtable test", true);
exec("deletemany", true, "\n");
exec("insert 0 0 0 0 -ts 0");
exec("insert 0 0 0 0 -l 0 -ts 0");
exec("insert 1 1 1 1 -ts 1");
exec("insert 2 2 2 2 -ts 2");
// prompts for delete, and rejects by default
exec("deletemany", true, "[SKIPPED] 0 0:0 []");
exec("deletemany -r 0", true, "[SKIPPED] 0 0:0 []");
exec("deletemany -r 0 -f", true, "[DELETED] 0 0:0 []");
// with auths, can delete the other record
exec("setauths -s 0");
exec("deletemany -r 0 -f", true, "[DELETED] 0 0:0 [0]");
// delete will show the timestamp
exec("deletemany -r 1 -f -st", true, "[DELETED] 1 1:1 [] 1");
// DeleteManyCommand has its own Formatter (DeleterFormatter), so it does not honor the -fm flag
exec("deletemany -r 2 -f -st -fm org.apache.accumulo.core.util.format.DateStringFormatter",
true, "[DELETED] 2 2:2 [] 2");
exec("setauths -c ", true);
exec("deletetable test -f", true, "Table: [test] has been deleted");
}
@Test
public void authsTest() throws Exception {
Shell.log.debug("Starting auths test --------------------------");
exec("setauths x,y,z", false, "Missing required option");
exec("setauths -s x,y,z -u notauser", false, "user does not exist");
exec("setauths -s y,z,x", true);
exec("getauths -u notauser", false, "user does not exist");
execExpectList("getauths", true, Arrays.asList("x", "y", "z"));
exec("addauths -u notauser", false, "Missing required option");
exec("addauths -u notauser -s foo", false, "user does not exist");
exec("addauths -s a", true);
execExpectList("getauths", true, Arrays.asList("x", "y", "z", "a"));
exec("setauths -c", true);
}
@Test
public void userExistsTest() throws IOException {
Shell.log.debug("Starting user test --------------------------");
String user = testName.getMethodName();
exec("createuser root", false, "user exists");
exec("createuser " + user, true);
exec("createuser " + user, false, "user exists");
exec("deleteuser " + user + " -f", true);
}
@Test
public void duContextTest() throws Exception {
Shell.log.debug("Starting du context test --------------------------");
exec("createtable t", true);
exec("du", true, "0 [t]");
exec("deletetable t -f", true, "Table: [t] has been deleted");
}
@Test
public void duTest() throws IOException {
Shell.log.debug("Starting DU test --------------------------");
exec("createtable t", true);
exec("du t", true, "0 [t]");
exec("deletetable t -f", true, "Table: [t] has been deleted");
}
@Test
public void duPatternTest() throws IOException {
Shell.log.debug("Starting DU with pattern test --------------------------");
exec("createtable t", true);
exec("createtable tt", true);
exec("du -p t.*", true, "0 [t, tt]");
exec("deletetable t -f", true, "Table: [t] has been deleted");
exec("deletetable tt -f", true, "Table: [tt] has been deleted");
}
@Test
public void scanTimestampTest() throws IOException {
Shell.log.debug("Starting scanTimestamp test ------------------------");
exec("createtable test", true);
exec("insert r f q v -ts 0", true);
exec("scan -st", true, "r f:q [] 0\tv");
exec("scan -st -f 0", true, " : [] 0\t");
exec("deletemany -f", true);
exec("deletetable test -f", true, "Table: [test] has been deleted");
}
@Test
public void scanFewTest() throws IOException {
Shell.log.debug("Starting scanFew test ------------------------");
exec("createtable test", true);
// historically, showing few did not pertain to ColVis or Timestamp
exec("insert 1 123 123456 -l '12345678' -ts 123456789 1234567890", true);
exec("setauths -s 12345678", true);
String expected = "1 123:123456 [12345678] 123456789\t1234567890";
String expectedFew = "1 123:12345 [12345678] 123456789\t12345";
exec("scan -st", true, expected);
exec("scan -st -f 5", true, expectedFew);
// also prove that BinaryFormatter behaves same as the default
exec("scan -st -fm org.apache.accumulo.core.util.format.BinaryFormatter", true, expected);
exec("scan -st -f 5 -fm org.apache.accumulo.core.util.format.BinaryFormatter", true,
expectedFew);
exec("setauths -c", true);
exec("deletetable test -f", true, "Table: [test] has been deleted");
}
@Test
public void scanDateStringFormatterTest() throws IOException {
Shell.log.debug("Starting scan dateStringFormatter test --------------------------");
exec("createtable t", true);
exec("insert r f q v -ts 0", true);
@SuppressWarnings("deprecation")
DateFormat dateFormat =
new SimpleDateFormat(org.apache.accumulo.core.util.format.DateStringFormatter.DATE_FORMAT);
String expected = String.format("r f:q [] %s\tv", dateFormat.format(new Date(0)));
// historically, showing few did not pertain to ColVis or Timestamp
String expectedNoTimestamp = "r f:q []\tv";
exec("scan -fm org.apache.accumulo.core.util.format.DateStringFormatter -st", true, expected);
exec("scan -fm org.apache.accumulo.core.util.format.DateStringFormatter -st -f 1000", true,
expected);
exec("scan -fm org.apache.accumulo.core.util.format.DateStringFormatter -st -f 5", true,
expected);
exec("scan -fm org.apache.accumulo.core.util.format.DateStringFormatter", true,
expectedNoTimestamp);
exec("deletetable t -f", true, "Table: [t] has been deleted");
}
@Test
public void grepTest() throws IOException {
Shell.log.debug("Starting grep test --------------------------");
exec("grep", false, "java.lang.IllegalStateException: Not in a table context");
exec("createtable t", true);
exec("setauths -s vis", true);
exec("insert r f q v -ts 0 -l vis", true);
String expected = "r f:q [vis]\tv";
String expectedTimestamp = "r f:q [vis] 0\tv";
exec("grep", false, "No terms specified");
exec("grep non_matching_string", true, "");
// historically, showing few did not pertain to ColVis or Timestamp
exec("grep r", true, expected);
exec("grep r -f 1", true, expected);
exec("grep r -st", true, expectedTimestamp);
exec("grep r -st -f 1", true, expectedTimestamp);
exec("setauths -c", true);
exec("deletetable t -f", true, "Table: [t] has been deleted");
}
@Test
public void commentTest() throws IOException {
Shell.log.debug("Starting comment test --------------------------");
exec("#", true, "Unknown command", false);
exec("# foo", true, "Unknown command", false);
exec("- foo", true, "Unknown command", true);
}
@Test
public void execFileTest() throws IOException {
Shell.log.debug("Starting exec file test --------------------------");
shell.config("--config-file", config.toString(), "-u", "root", "-p", getRootPassword(), "-zi",
getCluster().getInstanceName(), "-zh", getCluster().getZooKeepers(), "-f",
"src/main/resources/shellit.shellit");
assertEquals(0, shell.start());
assertGoodExit("Unknown command", false);
}
@Test
public void setIterTest() throws IOException {
Shell.log.debug("Starting setiter test --------------------------");
exec("createtable t", true);
String cmdJustClass = "setiter -class VersioningIterator -p 1";
exec(cmdJustClass, false, "java.lang.IllegalArgumentException", false);
exec(cmdJustClass, false, "fully qualified package name", true);
String cmdFullPackage = "setiter -class o.a.a.foo -p 1";
exec(cmdFullPackage, false, "java.lang.IllegalArgumentException", false);
exec(cmdFullPackage, false, "class not found", true);
String cmdNoOption = "setiter -class java.lang.String -p 1";
exec(cmdNoOption, false, "loaded successfully but does not implement SortedKeyValueIterator",
true);
input.set("\n\n");
exec(
"setiter -scan"
+ " -class org.apache.accumulo.core.iterators.ColumnFamilyCounter -p 30 -name foo",
true);
input.set("bar\nname value\n");
exec("setiter -scan -class org.apache.accumulo.core.iterators.ColumnFamilyCounter -p 31", true);
// TODO can't verify this as config -t fails, functionality verified in ShellServerIT
exec("deletetable t -f", true, "Table: [t] has been deleted");
}
}