/* * 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.mrit; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.MRJobConfig; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.NLineInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.util.Tool; import org.apache.hadoop.util.ToolRunner; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Run the Integration Tests as a Map-Reduce job. * <p> * Each of the Integration tests takes 30s to 20m to run. Using a larger cluster, all the tests can be run in parallel and finish much faster. * <p> * To run the tests, you first need a list of the tests. A simple way to get a list, is to scan the accumulo-test jar file for them. * * <pre> * $ jar -tf lib/accumulo-test.jar | grep IT.class | tr / . | sed -e 's/.class$//' >tests * </pre> * * Put the list of tests into HDFS: * * <pre> * $ hadoop fs -mkdir /tmp * $ hadoop fs -put tests /tmp/tests * </pre> * * Run the class below as a map-reduce job, giving it the lists of tests, and a place to store the results. * * <pre> * $ yarn jar lib/accumulo-test-mrit.jar -libjars lib/native/libaccumulo.so /tmp/tests /tmp/results * </pre> * * The result is a list of IT classes that pass or fail. Those classes that fail will be annotated with the particular test that failed within the class. */ public class IntegrationTestMapReduce extends Configured implements Tool { private static final Logger log = LoggerFactory.getLogger(IntegrationTestMapReduce.class); private static boolean isMapReduce = false; public static boolean isMapReduce() { return isMapReduce; } public static class TestMapper extends Mapper<LongWritable,Text,Text,Text> { static final Text FAIL = new Text("FAIL"); static final Text PASS = new Text("PASS"); static final Text ERROR = new Text("ERROR"); public static enum TestCounts { PASS, FAIL, ERROR } @Override protected void map(LongWritable key, Text value, final Mapper<LongWritable,Text,Text,Text>.Context context) throws IOException, InterruptedException { isMapReduce = true; String className = value.toString(); if (className.trim().isEmpty()) { return; } final List<String> failures = new ArrayList<>(); Class<? extends Object> test = null; try { test = Class.forName(className); } catch (ClassNotFoundException e) { log.debug("Error finding class {}", className, e); context.getCounter(TestCounts.ERROR).increment(1); context.write(ERROR, new Text(e.toString())); return; } log.info("Running test {}", className); JUnitCore core = new JUnitCore(); core.addListener(new RunListener() { @Override public void testStarted(Description description) throws Exception { log.info("Starting {}", description); context.progress(); } @Override public void testFinished(Description description) throws Exception { log.info("Finished {}", description); context.progress(); } @Override public void testFailure(Failure failure) throws Exception { log.info("Test failed: {}", failure.getDescription(), failure.getException()); failures.add(failure.getDescription().getMethodName()); context.progress(); } }); context.setStatus(test.getSimpleName()); try { Result result = core.run(test); if (result.wasSuccessful()) { log.info("{} was successful", className); context.getCounter(TestCounts.PASS).increment(1); context.write(PASS, value); } else { log.info("{} failed", className); context.getCounter(TestCounts.FAIL).increment(1); context.write(FAIL, new Text(className + "(" + StringUtils.join(failures, ", ") + ")")); } } catch (Exception e) { // most likely JUnit issues, like no tests to run log.info("Test failed: {}", className, e); } } } public static class TestReducer extends Reducer<Text,Text,Text,Text> { @Override protected void reduce(Text code, Iterable<Text> tests, Reducer<Text,Text,Text,Text>.Context context) throws IOException, InterruptedException { StringBuilder result = new StringBuilder("\n"); for (Text test : tests) { result.append(" "); result.append(test.toString()); result.append("\n"); } context.write(code, new Text(result.toString())); } } @Override public int run(String[] args) throws Exception { // read a list of tests from the input, and print out the results if (args.length != 2) { System.err.println("Wrong number of args: <input> <output>"); return 1; } Configuration conf = getConf(); Job job = Job.getInstance(conf, "accumulo integration test runner"); conf = job.getConfiguration(); // some tests take more than 10 minutes conf.setLong(MRJobConfig.TASK_TIMEOUT, 20 * 60 * 1000); // minicluster uses a lot of ram conf.setInt(MRJobConfig.MAP_MEMORY_MB, 4000); // hadoop puts an ancient version of jline on the classpath conf.setBoolean(MRJobConfig.MAPREDUCE_JOB_USER_CLASSPATH_FIRST, true); // no need to run a test multiple times job.setSpeculativeExecution(false); // read one line at a time job.setInputFormatClass(NLineInputFormat.class); NLineInputFormat.setNumLinesPerSplit(job, 1); // run the test job.setJarByClass(IntegrationTestMapReduce.class); job.setMapperClass(TestMapper.class); // group test by result code job.setReducerClass(TestReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); return job.waitForCompletion(true) ? 0 : 1; } public static void main(String[] args) throws Exception { System.exit(ToolRunner.run(new IntegrationTestMapReduce(), args)); } }