blob: 37448fee2138dab2acb2a1835fba8635961d62c8 [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.hadoop.hive.cli;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jline.console.ConsoleReader;
import jline.console.completer.ArgumentCompleter;
import jline.console.completer.Completer;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.common.io.SessionStream;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.metastore.api.Schema;
import org.apache.hadoop.hive.ql.IDriver;
import org.apache.hadoop.hive.ql.QueryState;
import org.apache.hadoop.hive.ql.processors.CommandProcessorException;
import org.apache.hadoop.hive.ql.processors.CommandProcessorResponse;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.After;
// Cannot call class TestCliDriver since that's the name of the generated
// code for the script-based testing
/**
* TestCliDriverMethods.
*/
public class TestCliDriverMethods {
SecurityManager securityManager;
// Some of these tests require intercepting System.exit() using the SecurityManager.
// It is safer to register/unregister our SecurityManager during setup/teardown instead
// of doing it within the individual test cases.
@Before
public void setUp() {
securityManager = System.getSecurityManager();
System.setSecurityManager(new NoExitSecurityManager(securityManager));
}
@After
public void tearDown() {
System.setSecurityManager(securityManager);
}
// If the command has an associated schema, make sure it gets printed to use
@Test
public void testThatCliDriverPrintsHeaderForCommandsWithSchema() throws CommandProcessorException {
Schema mockSchema = mock(Schema.class);
List<FieldSchema> fieldSchemas = new ArrayList<FieldSchema>();
String fieldName = "FlightOfTheConchords";
fieldSchemas.add(new FieldSchema(fieldName, "type", "comment"));
when(mockSchema.getFieldSchemas()).thenReturn(fieldSchemas);
PrintStream mockOut = headerPrintingTestDriver(mockSchema);
// Should have printed out the header for the field schema
verify(mockOut, times(1)).print(fieldName);
}
// If the command has no schema, make sure nothing is printed
@Test
public void testThatCliDriverPrintsNoHeaderForCommandsWithNoSchema() throws CommandProcessorException {
Schema mockSchema = mock(Schema.class);
when(mockSchema.getFieldSchemas()).thenReturn(null);
PrintStream mockOut = headerPrintingTestDriver(mockSchema);
// Should not have tried to print any thing.
verify(mockOut, never()).print(anyString());
}
// Test that CliDriver does not strip comments starting with '--'
@Test
public void testThatCliDriverDoesNotStripComments() throws Exception {
// We need to overwrite System.out and System.err as that is what is used in ShellCmdExecutor
// So save old values...
PrintStream oldOut = System.out;
PrintStream oldErr = System.err;
// Capture stdout and stderr
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
SessionStream out = new SessionStream(dataOut);
System.setOut(out);
ByteArrayOutputStream dataErr = new ByteArrayOutputStream();
SessionStream err = new SessionStream(dataErr);
System.setErr(err);
CliSessionState ss = new CliSessionState(new HiveConf());
ss.out = out;
ss.err = err;
// Save output as yo cannot print it while System.out and System.err are weird
String message;
String errors;
try {
CliSessionState.start(ss);
CliDriver cliDriver = new CliDriver();
// issue a command with bad options
cliDriver.processCmd("!ls --abcdefghijklmnopqrstuvwxyz123456789");
assertTrue("Comments with '--; should not have been stripped, so command should fail", false);
} catch (CommandProcessorException e) {
// this is expected to happen
} finally {
// restore System.out and System.err
System.setOut(oldOut);
System.setErr(oldErr);
}
message = dataOut.toString("UTF-8");
errors = dataErr.toString("UTF-8");
assertTrue("Comments with '--; should not have been stripped,"
+ " so we should have got an error in the output: '" + errors + "'.",
errors.contains("option"));
assertNotNull(message); // message kept around in for debugging
}
/**
* Do the actual testing against a mocked CliDriver based on what type of schema
*
* @param mockSchema
* Schema to throw against test
* @return Output that would have been sent to the user
* @throws CommandProcessorException
* @throws CommandNeedRetryException
* won't actually be thrown
*/
private PrintStream headerPrintingTestDriver(Schema mockSchema) throws CommandProcessorException {
CliDriver cliDriver = new CliDriver();
// We want the driver to try to print the header...
Configuration conf = mock(Configuration.class);
when(conf.getBoolean(eq(ConfVars.HIVE_CLI_PRINT_HEADER.varname), anyBoolean()))
.thenReturn(true);
cliDriver.setConf(conf);
IDriver proc = mock(IDriver.class);
CommandProcessorResponse cpr = mock(CommandProcessorResponse.class);
QueryState queryState = new QueryState.Builder().withGenerateNewQueryId(true).build();
when(proc.run(anyString())).thenReturn(cpr);
when(proc.getQueryState()).thenReturn(queryState);
// and then see what happens based on the provided schema
when(proc.getSchema()).thenReturn(mockSchema);
CliSessionState mockSS = mock(CliSessionState.class);
SessionStream mockOut = mock(SessionStream.class);
mockSS.out = mockOut;
cliDriver.processLocalCmd("use default;", proc, mockSS);
return mockOut;
}
@Test
public void testGetCommandCompletor() {
Completer[] completors = CliDriver.getCommandCompleter();
assertEquals(2, completors.length);
assertTrue(completors[0] instanceof ArgumentCompleter);
assertTrue(completors[1] instanceof Completer);
List<CharSequence> testList = Arrays.asList(")");
completors[1].complete("fdsdfsdf", 0, testList);
assertEquals(")", testList.get(0));
testList=new ArrayList<CharSequence>();
completors[1].complete("len", 0, testList);
assertTrue(testList.get(0).toString().endsWith("length("));
testList=new ArrayList<CharSequence>();
completors[0].complete("set f", 0, testList);
assertEquals("set", testList.get(0));
}
@Test
public void testRun() throws Exception {
// clean history
String historyDirectory = System.getProperty("user.home");
if ((new File(historyDirectory)).exists()) {
File historyFile = new File(historyDirectory + File.separator + ".hivehistory");
historyFile.delete();
}
HiveConf configuration = new HiveConf();
configuration.setBoolVar(ConfVars.HIVE_SESSION_HISTORY_ENABLED, true);
PrintStream oldOut = System.out;
ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(dataOut));
PrintStream oldErr = System.err;
ByteArrayOutputStream dataErr = new ByteArrayOutputStream();
System.setErr(new PrintStream(dataErr));
CliSessionState ss = new CliSessionState(configuration);
CliSessionState.start(ss);
String[] args = {};
try {
new FakeCliDriver().run(args);
assertTrue(dataOut.toString(), dataOut.toString().contains("test message"));
assertTrue(dataErr.toString(), dataErr.toString().contains("Hive history file="));
assertTrue(dataErr.toString(), dataErr.toString().contains("File: fakeFile is not a file."));
dataOut.reset();
dataErr.reset();
} finally {
System.setOut(oldOut);
System.setErr(oldErr);
}
}
/**
* Test commands exit and quit
*/
@Test
public void testQuit() throws Exception {
CliSessionState ss = new CliSessionState(new HiveConf());
ss.err = new SessionStream(System.err);
ss.out = new SessionStream(System.out);
try {
CliSessionState.start(ss);
CliDriver cliDriver = new CliDriver();
cliDriver.processCmd("quit");
fail("should be exit");
} catch (ExitException e) {
assertEquals(0, e.getStatus());
} catch (Exception e) {
throw e;
}
try {
CliSessionState.start(ss);
CliDriver cliDriver = new CliDriver();
cliDriver.processCmd("exit");
fail("should be exit");
} catch (ExitException e) {
assertEquals(0, e.getStatus());
}
}
@Test
public void testProcessSelectDatabase() throws Exception {
CliSessionState sessinState = new CliSessionState(new HiveConf());
CliSessionState.start(sessinState);
ByteArrayOutputStream data = new ByteArrayOutputStream();
sessinState.err = new SessionStream(data);
sessinState.database = "database";
CliDriver driver = new CliDriver();
try {
driver.processSelectDatabase(sessinState);
fail("shuld be exit");
} catch (ExitException e) {
e.printStackTrace();
assertEquals(40000, e.getStatus());
}
assertTrue(data.toString().contains(
"FAILED: ParseException line 1:4 cannot recognize input near 'database'"));
}
@Test
public void testprocessInitFiles() throws Exception {
String oldHiveHome = System.getenv("HIVE_HOME");
String oldHiveConfDir = System.getenv("HIVE_CONF_DIR");
File homeFile = File.createTempFile("test", "hive");
String tmpDir = homeFile.getParentFile().getAbsoluteFile() + File.separator
+ "TestCliDriverMethods";
homeFile.delete();
FileUtils.deleteDirectory(new File(tmpDir));
homeFile = new File(tmpDir + File.separator + "bin" + File.separator + CliDriver.HIVERCFILE);
homeFile.getParentFile().mkdirs();
homeFile.createNewFile();
FileUtils.write(homeFile, "-- init hive file for test ");
setEnv("HIVE_HOME", homeFile.getParentFile().getParentFile().getAbsolutePath());
setEnv("HIVE_CONF_DIR", homeFile.getParentFile().getAbsolutePath());
CliSessionState sessionState = new CliSessionState(new HiveConf());
ByteArrayOutputStream data = new ByteArrayOutputStream();
sessionState.err = new SessionStream(data);
sessionState.out = new SessionStream(System.out);
sessionState.setIsQtestLogging(true);
try {
CliSessionState.start(sessionState);
CliDriver cliDriver = new CliDriver();
cliDriver.processInitFiles(sessionState);
assertTrue(data.toString().contains(
"Putting the global hiverc in $HIVE_HOME/bin/.hiverc is deprecated. " +
"Please use $HIVE_CONF_DIR/.hiverc instead."));
FileUtils.write(homeFile, "bla bla bla");
// if init file contains incorrect row
try {
cliDriver.processInitFiles(sessionState);
fail("should be exit");
} catch (ExitException e) {
assertEquals(40000, e.getStatus());
}
setEnv("HIVE_HOME", null);
try {
cliDriver.processInitFiles(sessionState);
fail("should be exit");
} catch (ExitException e) {
assertEquals(40000, e.getStatus());
}
} finally {
// restore data
setEnv("HIVE_HOME", oldHiveHome);
setEnv("HIVE_CONF_DIR", oldHiveConfDir);
FileUtils.deleteDirectory(new File(tmpDir));
}
File f = File.createTempFile("hive", "test");
FileUtils.write(f, "bla bla bla");
try {
sessionState.initFiles = Arrays.asList(new String[] {f.getAbsolutePath()});
CliDriver cliDriver = new CliDriver();
cliDriver.processInitFiles(sessionState);
fail("should be exit");
} catch (ExitException e) {
assertEquals(40000, e.getStatus());
assertTrue(data.toString().contains("cannot recognize input near 'bla' 'bla' 'bla'"));
}
}
@Test
public void testCommandSplits() {
// Test double quote in the string
String cmd1 = "insert into escape1 partition (ds='1', part='\"') values (\"!\")";
assertEquals(cmd1, CliDriver.splitSemiColon(cmd1).get(0));
assertEquals(cmd1, CliDriver.splitSemiColon(cmd1 + ";").get(0));
// Test escape
String cmd2 = "insert into escape1 partition (ds='1', part='\"\\'') values (\"!\")";
assertEquals(cmd2, CliDriver.splitSemiColon(cmd2).get(0));
assertEquals(cmd2, CliDriver.splitSemiColon(cmd2 + ";").get(0));
// Test multiple commands
List<String> results = CliDriver.splitSemiColon(cmd1 + ";" + cmd2);
assertEquals(cmd1, results.get(0));
assertEquals(cmd2, results.get(1));
results = CliDriver.splitSemiColon(cmd1 + ";" + cmd2 + ";");
assertEquals(cmd1, results.get(0));
assertEquals(cmd2, results.get(1));
}
private static void setEnv(String key, String value) throws Exception {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for (Class cl : classes) {
if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
if (value == null) {
map.remove(key);
} else {
map.put(key, value);
}
}
}
}
private static class FakeCliDriver extends CliDriver {
@Override
protected void setupConsoleReader() throws IOException {
reader = new FakeConsoleReader();
}
}
private static class FakeConsoleReader extends ConsoleReader {
private int counter = 0;
File temp = null;
public FakeConsoleReader() throws IOException {
super();
}
@Override
public String readLine(String prompt) throws IOException {
FileWriter writer;
switch (counter++) {
case 0:
return "!echo test message;";
case 1:
temp = File.createTempFile("hive", "test");
temp.deleteOnExit();
return "source " + temp.getAbsolutePath() + ";";
case 2:
temp = File.createTempFile("hive", "test");
temp.deleteOnExit();
writer = new FileWriter(temp);
writer.write("bla bla bla");
writer.close();
return "list file file://" + temp.getAbsolutePath() + ";";
case 3:
return "!echo ";
case 4:
return "test message;";
case 5:
return "source fakeFile;";
case 6:
temp = File.createTempFile("hive", "test");
temp.deleteOnExit();
writer = new FileWriter(temp);
writer.write("source fakeFile;");
writer.close();
return "list file file://" + temp.getAbsolutePath() + ";";
// drop table over10k;
default:
return null;
}
}
}
private static class NoExitSecurityManager extends SecurityManager {
public SecurityManager parentSecurityManager;
public NoExitSecurityManager(SecurityManager parent) {
super();
parentSecurityManager = parent;
System.setSecurityManager(this);
}
@Override
public void checkPermission(Permission perm, Object context) {
if (parentSecurityManager != null) {
parentSecurityManager.checkPermission(perm, context);
}
}
@Override
public void checkPermission(Permission perm) {
if (parentSecurityManager != null) {
parentSecurityManager.checkPermission(perm);
}
}
@Override
public void checkExit(int status) {
throw new ExitException(status);
}
}
private static class ExitException extends RuntimeException {
int status;
public ExitException(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
}
}