/** * 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.cli.util.*; import org.apache.hadoop.cli.util.CLITestCmd; import org.apache.hadoop.cli.util.CLICommand; import org.apache.hadoop.cli.util.CommandExecutor.Result; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.util.StringUtils; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.File; import java.util.ArrayList; /** * Tests for the Command Line Interface (CLI) */ public class CLITestHelper { private static final Log LOG = LogFactory.getLog(CLITestHelper.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 protected String testMode = TESTMODE_TEST; // Storage for tests read in from the config file protected ArrayList<CLITestData> testsFromConfigFile = null; protected ArrayList<ComparatorData> testComparators = null; protected String thisTestCaseName = null; protected ComparatorData comparatorData = null; protected Configuration conf = null; protected String clitestDataDir = null; protected String username = null; /** * Read the test config file - testConfig.xml */ protected void readTestConfigFile() { String testConfigFile = getTestFile(); if (testsFromConfigFile == null) { boolean success = false; testConfigFile = TEST_CACHE_DATA_DIR + File.separator + testConfigFile; try { SAXParser p = (SAXParserFactory.newInstance()).newSAXParser(); p.parse(testConfigFile, getConfigParser()); success = true; } catch (Exception e) { LOG.info("File: " + testConfigFile + " not found"); success = false; } assertTrue("Error reading test config file", success); } } /** * Method decides what is a proper configuration file parser for this type * of CLI tests. * Ancestors need to override the implementation if a parser with additional * features is needed. Also, such ancestor has to provide its own * TestConfigParser implementation * @return an instance of TestConfigFileParser class */ protected TestConfigFileParser getConfigParser () { return new TestConfigFileParser(); } protected String getTestFile() { return ""; } /* * Setup */ public void setUp() throws Exception { // Read the testConfig.xml file readTestConfigFile(); conf = new Configuration(); conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); clitestDataDir = new File(TEST_CACHE_DATA_DIR). toURI().toString().replace(' ', '+'); } /** * Tear down */ public void tearDown() throws Exception { displayResults(); } /** * Expand the commands from the test config xml file * @param cmd * @return String expanded command */ protected String expandCommand(final String cmd) { String expCmd = cmd; 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<CLICommand> testCommands = td.getTestCommands(); for (CLICommand cmd : testCommands) { LOG.info(" Test Commands: [" + expandCommand(cmd.getCmd()) + "]"); } LOG.info(""); ArrayList<CLICommand> cleanupCommands = td.getCleanupCommands(); for (CLICommand 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: [" + expandCommand(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 ---")); if ((totalPass + totalFail) == 0) { LOG.info(" # Tests pass: " + 0); LOG.info(" # Tests fail: " + 0); } else { 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, Result cmdResult) { // 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(cmdResult.getCommandOutput(), expandCommand(compdata.getExpectedOutput())); } catch (Exception e) { LOG.info("Error in instantiating the comparator" + e); } } return compareOutput; } /*********************************** ************* TESTS RUNNER *********************************/ public void testAll() { assertTrue("Number of tests has to be greater then zero", testsFromConfigFile.size() > 0); LOG.info("TestAll"); // Run the tests defined in the testConf.xml config file. for (int index = 0; index < testsFromConfigFile.size(); index++) { CLITestData testdata = testsFromConfigFile.get(index); // Execute the test commands ArrayList<CLICommand> testCommands = testdata.getTestCommands(); Result cmdResult = null; for (CLICommand cmd : testCommands) { try { cmdResult = execute(cmd); } 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, cmdResult); overallTCResult &= compareOutput; } cd.setExitCode(cmdResult.getExitCode()); cd.setActualOutput(cmdResult.getCommandOutput()); cd.setTestResult(compareOutput); } testdata.setTestResult(overallTCResult); // Execute the cleanup commands ArrayList<CLICommand> cleanupCommands = testdata.getCleanupCommands(); for (CLICommand cmd : cleanupCommands) { try { execute(cmd); } catch (Exception e) { fail(StringUtils.stringifyException(e)); } } } } /** * this method has to be overridden by an ancestor */ protected CommandExecutor.Result execute(CLICommand cmd) throws Exception { throw new Exception("Unknown type of test command:"+ cmd.getType()); } /* * Parser class for the test config xml file */ class TestConfigFileParser extends DefaultHandler { String charString = null; CLITestData td = null; ArrayList<CLICommand> testCommands = null; ArrayList<CLICommand> 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<CLICommand>(); } else if (qName.equals("cleanup-commands")) { cleanupCommands = new ArrayList<CLICommand>(); } 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 CLITestCmd(charString, new CLICommandFS())); } else if (cleanupCommands != null) { cleanupCommands.add(new CLITestCmd(charString, new CLICommandFS())); } } 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; } } }