/** * 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.cli; import java.io.File; import java.util.ArrayList; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.cli.util.CLITestData; import org.apache.hadoop.cli.util.CommandExecutor; import org.apache.hadoop.cli.util.ComparatorBase; import org.apache.hadoop.cli.util.ComparatorData; import org.apache.hadoop.cli.util.CLITestData.TestCmd; import org.apache.hadoop.cli.util.CLITestData.TestCmd.CommandType; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.MiniMRCluster; import org.apache.hadoop.security.authorize.HadoopPolicyProvider; import org.apache.hadoop.security.authorize.PolicyProvider; import org.apache.hadoop.security.authorize.ServiceAuthorizationManager; import org.apache.hadoop.util.StringUtils; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Tests for the Command Line Interface (CLI) */ public class TestCLI extends TestCase { private static final Log LOG = LogFactory.getLog(TestCLI.class.getName()); // In this mode, it runs the command and compares the actual output // with the expected output public static final String TESTMODE_TEST = "test"; // Run the tests // If it is set to nocompare, run the command and do not compare. // This can be useful populate the testConfig.xml file the first time // a new command is added public static final String TESTMODE_NOCOMPARE = "nocompare"; public static final String TEST_CACHE_DATA_DIR = System.getProperty("test.cache.data", "build/test/cache"); //By default, run the tests. The other mode is to run the commands and not // compare the output public static String testMode = TESTMODE_TEST; // Storage for tests read in from the config file static ArrayList<CLITestData> testsFromConfigFile = null; static ArrayList<ComparatorData> testComparators = null; static String testConfigFile = "testConf.xml"; String thisTestCaseName = null; static ComparatorData comparatorData = null; private static Configuration conf = null; private static MiniDFSCluster dfsCluster = null; private static DistributedFileSystem dfs = null; private static MiniMRCluster mrCluster = null; private static String namenode = null; private static String jobtracker = null; private static String clitestDataDir = null; private static String username = null; /** * Read the test config file - testConfig.xml */ private void readTestConfigFile() { if (testsFromConfigFile == null) { boolean success = false; testConfigFile = TEST_CACHE_DATA_DIR + File.separator + testConfigFile; try { SAXParser p = (SAXParserFactory.newInstance()).newSAXParser(); p.parse(testConfigFile, new TestConfigFileParser()); success = true; } catch (Exception e) { LOG.info("File: " + testConfigFile + " not found"); success = false; } assertTrue("Error reading test config file", success); } } /* * Setup */ public void setUp() throws Exception { // Read the testConfig.xml file readTestConfigFile(); // Start up the mini dfs cluster boolean success = false; conf = new Configuration(); conf.setClass(PolicyProvider.POLICY_PROVIDER_CONFIG, HadoopPolicyProvider.class, PolicyProvider.class); conf.setBoolean(ServiceAuthorizationManager.SERVICE_AUTHORIZATION_CONFIG, true); dfsCluster = new MiniDFSCluster(conf, 1, true, null); namenode = conf.get("fs.default.name", "file:///"); clitestDataDir = new File(TEST_CACHE_DATA_DIR). toURI().toString().replace(' ', '+'); username = System.getProperty("user.name"); FileSystem fs = dfsCluster.getFileSystem(); assertTrue("Not a HDFS: "+fs.getUri(), fs instanceof DistributedFileSystem); dfs = (DistributedFileSystem) fs; // Start up mini mr cluster JobConf mrConf = new JobConf(conf); mrCluster = new MiniMRCluster(1, dfsCluster.getFileSystem().getUri().toString(), 1, null, null, mrConf); jobtracker = mrCluster.createJobConf().get("mapred.job.tracker", "local"); success = true; assertTrue("Error setting up Mini DFS & MR clusters", success); } /** * Tear down */ public void tearDown() throws Exception { boolean success = false; mrCluster.shutdown(); dfs.close(); dfsCluster.shutdown(); success = true; Thread.sleep(2000); assertTrue("Error tearing down Mini DFS & MR clusters", success); displayResults(); } /** * Expand the commands from the test config xml file * @param cmd * @return String expanded command */ private String expandCommand(final String cmd) { String expCmd = cmd; expCmd = expCmd.replaceAll("NAMENODE", namenode); expCmd = expCmd.replaceAll("JOBTRACKER", jobtracker); expCmd = expCmd.replaceAll("CLITEST_DATA", clitestDataDir); expCmd = expCmd.replaceAll("USERNAME", username); return expCmd; } /** * Display the summarized results */ private void displayResults() { LOG.info("Detailed results:"); LOG.info("----------------------------------\n"); for (int i = 0; i < testsFromConfigFile.size(); i++) { CLITestData td = testsFromConfigFile.get(i); boolean testResult = td.getTestResult(); // Display the details only if there is a failure if (!testResult) { LOG.info("-------------------------------------------"); LOG.info(" Test ID: [" + (i + 1) + "]"); LOG.info(" Test Description: [" + td.getTestDesc() + "]"); LOG.info(""); ArrayList<TestCmd> testCommands = td.getTestCommands(); for (TestCmd cmd : testCommands) { LOG.info(" Test Commands: [" + expandCommand(cmd.getCmd()) + "]"); } LOG.info(""); ArrayList<TestCmd> cleanupCommands = td.getCleanupCommands(); for (TestCmd cmd : cleanupCommands) { LOG.info(" Cleanup Commands: [" + expandCommand(cmd.getCmd()) + "]"); } LOG.info(""); ArrayList<ComparatorData> compdata = td.getComparatorData(); for (ComparatorData cd : compdata) { boolean resultBoolean = cd.getTestResult(); LOG.info(" Comparator: [" + cd.getComparatorType() + "]"); LOG.info(" Comparision result: [" + (resultBoolean ? "pass" : "fail") + "]"); LOG.info(" Expected output: [" + cd.getExpectedOutput() + "]"); LOG.info(" Actual output: [" + cd.getActualOutput() + "]"); } LOG.info(""); } } LOG.info("Summary results:"); LOG.info("----------------------------------\n"); boolean overallResults = true; int totalPass = 0; int totalFail = 0; int totalComparators = 0; for (int i = 0; i < testsFromConfigFile.size(); i++) { CLITestData td = testsFromConfigFile.get(i); totalComparators += testsFromConfigFile.get(i).getComparatorData().size(); boolean resultBoolean = td.getTestResult(); if (resultBoolean) { totalPass ++; } else { totalFail ++; } overallResults &= resultBoolean; } LOG.info(" Testing mode: " + testMode); LOG.info(""); LOG.info(" Overall result: " + (overallResults ? "+++ PASS +++" : "--- FAIL ---")); LOG.info(" # Tests pass: " + totalPass + " (" + (100 * totalPass / (totalPass + totalFail)) + "%)"); LOG.info(" # Tests fail: " + totalFail + " (" + (100 * totalFail / (totalPass + totalFail)) + "%)"); LOG.info(" # Validations done: " + totalComparators + " (each test may do multiple validations)"); LOG.info(""); LOG.info("Failing tests:"); LOG.info("--------------"); int i = 0; boolean foundTests = false; for (i = 0; i < testsFromConfigFile.size(); i++) { boolean resultBoolean = testsFromConfigFile.get(i).getTestResult(); if (!resultBoolean) { LOG.info((i + 1) + ": " + testsFromConfigFile.get(i).getTestDesc()); foundTests = true; } } if (!foundTests) { LOG.info("NONE"); } foundTests = false; LOG.info(""); LOG.info("Passing tests:"); LOG.info("--------------"); for (i = 0; i < testsFromConfigFile.size(); i++) { boolean resultBoolean = testsFromConfigFile.get(i).getTestResult(); if (resultBoolean) { LOG.info((i + 1) + ": " + testsFromConfigFile.get(i).getTestDesc()); foundTests = true; } } if (!foundTests) { LOG.info("NONE"); } assertTrue("One of the tests failed. " + "See the Detailed results to identify " + "the command that failed", overallResults); } /** * Compare the actual output with the expected output * @param compdata * @return */ private boolean compareTestOutput(ComparatorData compdata) { // Compare the output based on the comparator String comparatorType = compdata.getComparatorType(); Class<?> comparatorClass = null; // If testMode is "test", then run the command and compare the output // If testMode is "nocompare", then run the command and dump the output. // Do not compare boolean compareOutput = false; if (testMode.equals(TESTMODE_TEST)) { try { // Initialize the comparator class and run its compare method comparatorClass = Class.forName("org.apache.hadoop.cli.util." + comparatorType); ComparatorBase comp = (ComparatorBase) comparatorClass.newInstance(); compareOutput = comp.compare(CommandExecutor.getLastCommandOutput(), compdata.getExpectedOutput()); } catch (Exception e) { LOG.info("Error in instantiating the comparator" + e); } } return compareOutput; } /*********************************** ************* TESTS *********************************/ public void testAll() { LOG.info("TestAll"); // Run the tests defined in the testConf.xml config file. for (int index = 0; index < testsFromConfigFile.size(); index++) { CLITestData testdata = (CLITestData) testsFromConfigFile.get(index); // Execute the test commands ArrayList<TestCmd> testCommands = testdata.getTestCommands(); for (TestCmd cmd : testCommands) { try { CommandExecutor.executeCommand(cmd, namenode, jobtracker); } catch (Exception e) { fail(StringUtils.stringifyException(e)); } } boolean overallTCResult = true; // Run comparators ArrayList<ComparatorData> compdata = testdata.getComparatorData(); for (ComparatorData cd : compdata) { final String comptype = cd.getComparatorType(); boolean compareOutput = false; if (! comptype.equalsIgnoreCase("none")) { compareOutput = compareTestOutput(cd); overallTCResult &= compareOutput; } cd.setExitCode(CommandExecutor.getLastExitCode()); cd.setActualOutput(CommandExecutor.getLastCommandOutput()); cd.setTestResult(compareOutput); } testdata.setTestResult(overallTCResult); // Execute the cleanup commands ArrayList<TestCmd> cleanupCommands = testdata.getCleanupCommands(); for (TestCmd cmd : cleanupCommands) { try { CommandExecutor.executeCommand(cmd, namenode, jobtracker); } catch (Exception e) { fail(StringUtils.stringifyException(e)); } } } } /* * Parser class for the test config xml file */ static class TestConfigFileParser extends DefaultHandler { String charString = null; CLITestData td = null; ArrayList<TestCmd> testCommands = null; ArrayList<TestCmd> cleanupCommands = null; @Override public void startDocument() throws SAXException { testsFromConfigFile = new ArrayList<CLITestData>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals("test")) { td = new CLITestData(); } else if (qName.equals("test-commands")) { testCommands = new ArrayList<TestCmd>(); } else if (qName.equals("cleanup-commands")) { cleanupCommands = new ArrayList<TestCmd>(); } else if (qName.equals("comparators")) { testComparators = new ArrayList<ComparatorData>(); } else if (qName.equals("comparator")) { comparatorData = new ComparatorData(); } charString = ""; } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (qName.equals("description")) { td.setTestDesc(charString); } else if (qName.equals("test-commands")) { td.setTestCommands(testCommands); testCommands = null; } else if (qName.equals("cleanup-commands")) { td.setCleanupCommands(cleanupCommands); cleanupCommands = null; } else if (qName.equals("command")) { if (testCommands != null) { testCommands.add(new TestCmd(charString, CommandType.FS)); } else if (cleanupCommands != null) { cleanupCommands.add(new TestCmd(charString, CommandType.FS)); } } else if (qName.equals("dfs-admin-command")) { if (testCommands != null) { testCommands.add(new TestCmd(charString,CommandType.DFSADMIN)); } else if (cleanupCommands != null) { cleanupCommands.add(new TestCmd(charString, CommandType.DFSADMIN)); } } else if (qName.equals("mr-admin-command")) { if (testCommands != null) { testCommands.add(new TestCmd(charString,CommandType.MRADMIN)); } else if (cleanupCommands != null) { cleanupCommands.add(new TestCmd(charString, CommandType.MRADMIN)); } } else if (qName.equals("comparators")) { td.setComparatorData(testComparators); } else if (qName.equals("comparator")) { testComparators.add(comparatorData); } else if (qName.equals("type")) { comparatorData.setComparatorType(charString); } else if (qName.equals("expected-output")) { comparatorData.setExpectedOutput(charString); } else if (qName.equals("test")) { testsFromConfigFile.add(td); td = null; } else if (qName.equals("mode")) { testMode = charString; if (!testMode.equals(TESTMODE_NOCOMPARE) && !testMode.equals(TESTMODE_TEST)) { testMode = TESTMODE_TEST; } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { String s = new String(ch, start, length); charString += s; } } }