package org.mapfish.print; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter; import com.google.common.base.Strings; import com.google.common.collect.Maps; import com.google.common.io.Files; import org.apache.commons.lang.ArrayUtils; import org.json.JSONObject; import org.junit.BeforeClass; import org.junit.Test; import org.junit.internal.TextListener; import org.junit.runner.JUnitCore; import org.junit.runner.RunWith; import org.junit.runner.notification.RunListener; import org.mapfish.print.servlet.MapPrinterServlet; import org.mapfish.print.servlet.oldapi.OldAPIRequestConverter; import org.mapfish.print.test.util.ImageSimilarity; import org.mapfish.print.wrapper.json.PJsonObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.net.URL; import java.nio.charset.Charset; import java.util.Map; import java.util.regex.Pattern; import javax.imageio.ImageIO; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mapfish.print.servlet.MapPrinterServlet.JSON_ATTRIBUTES; import static org.mapfish.print.servlet.MapPrinterServlet.JSON_REQUEST_HEADERS; /** * To run this test make sure that the test GeoServer is running: * <p></p> * ./gradlew examples:farmRun * <p></p> * Or run the tests with the following task (which automatically starts the server): * <p></p> * ./gradlew examples:farmIntegrationTest */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { ExamplesTest.DEFAULT_SPRING_XML, ExamplesTest.TEST_SPRING_XML }) public class ExamplesTest { private static final Logger LOGGER = LoggerFactory.getLogger(ExamplesTest.class); public static final String DEFAULT_SPRING_XML = "classpath:mapfish-spring-application-context.xml"; public static final String TEST_SPRING_XML = "classpath:test-http-request-factory-application-context.xml"; private static final String REQUEST_DATA_FILE = "requestData(-.*)?.json"; private static final String OLD_API_REQUEST_DATA_FILE = "oldApi-requestData(-.*)?.json"; private static final String CONFIG_FILE = "config.yaml"; /** * If this system property is set then it will be interpreted as a regular expression and will be used to filter the * examples that are run. * * For example: * -Dexamples.filter=verbose.* * * will run all examples starting with verbose. */ private static final String FILTER_PROPERTY = "examples.filter"; private static final Pattern REQUEST_MATCH_ALL = Pattern.compile(".*"); private static final Pattern EXAMPLE_MATCH_ALL = Pattern.compile(".*"); @Autowired MapPrinter mapPrinter; private static Pattern exampleFilter; private static Pattern requestFilter; @BeforeClass public static void setUp() throws Exception { final ClassLoader classLoader = AbstractApiTest.class.getClassLoader(); final URL logfile = classLoader.getResource("logback.xml"); final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); try { final JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(loggerContext); // Call context.reset() to clear any previous configuration, e.g. default // configuration. For multi-step configuration, omit calling context.reset(). loggerContext.reset(); configurator.doConfigure(logfile); } catch (JoranException je) { // StatusPrinter will handle this } StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext); String filterProperty = System.getProperty(FILTER_PROPERTY); if (!Strings.isNullOrEmpty(filterProperty)) { String[] parts = filterProperty.split("/", 2); if (parts.length == 1) { requestFilter = REQUEST_MATCH_ALL; } else { requestFilter = Pattern.compile(parts[1]); } exampleFilter = Pattern.compile(parts[0]); } else { requestFilter = REQUEST_MATCH_ALL; exampleFilter = EXAMPLE_MATCH_ALL; } } @Test public void testExampleDirectoryNames() throws Exception { final String namePattern = "[a-zA-Z0-9_]+"; final File examplesDir = getFile(ExamplesTest.class, "/examples"); StringBuilder errors = new StringBuilder(); for (File example : Files.fileTreeTraverser().children(examplesDir)) { if (example.isDirectory() && !examplesDir.getName().matches(namePattern)) { errors.append("\n * ").append(examplesDir.getName()); } } assertEquals("All example directory names must match the pattern: '" + namePattern + "'. The following fail that test: " + errors, 0, errors.length()); } @Test public void testAllExamples() throws Exception { Map<String, Throwable> errors = Maps.newHashMap(); int testsRan = 0; final File examplesDir = getFile(ExamplesTest.class, "/examples"); for (File example : Files.fileTreeTraverser().children(examplesDir)) { if (example.isDirectory() && exampleFilter.matcher(example.getName()).matches()) { testsRan += runExample(example, errors); } } if (!errors.isEmpty()) { for (Map.Entry<String, Throwable> error : errors.entrySet()) { System.err.println("\nExample: '" + error.getKey() + "' failed with the error:"); error.getValue().printStackTrace(); } StringBuilder errorReport = new StringBuilder(); errorReport.append("\n"); errorReport.append(errors.size()); errorReport.append(" errors encountered while running "); errorReport.append(testsRan); errorReport.append(" examples.\n"); errorReport.append("See Standard Error for the stack traces. A summary is as follows...\n\n"); for (Map.Entry<String, Throwable> error : errors.entrySet()) { StringBuilder exampleName = new StringBuilder(); exampleName.append("The example "); exampleName.append(error.getKey()); errorReport.append(exampleName); errorReport.append('\n'); errorReport.append(exampleName.toString().replaceAll(".", "=")); errorReport.append('\n'); errorReport.append("Failed with the error:\n"); errorReport.append(error.getValue()); errorReport.append('\n'); } errorReport.append("\n\n"); fail(errorReport.toString()); } } private int runExample(File example, Map<String, Throwable> errors) { int testsRan = 0; try { final File configFile = new File(example, CONFIG_FILE); this.mapPrinter.setConfiguration(configFile); if (!hasRequestFile(example)) { throw new AssertionError("Example: '" + example.getName() + "' does not have any request data files."); } for (File requestFile : Files.fileTreeTraverser().children(example)) { if (!requestFile.isFile() || !requestFilter.matcher(requestFile.getName()).matches()) { continue; } try { if (isRequestDataFile(requestFile)) { // WARN to be displayed in the Travis logs LOGGER.warn("Run example '{}' ({})", example.getName(), requestFile.getName()); String requestData = Files.asCharSource(requestFile, Charset.forName(Constants.DEFAULT_ENCODING)).read(); final PJsonObject jsonSpec; if (requestFile.getName().matches(OLD_API_REQUEST_DATA_FILE)) { PJsonObject oldSpec = MapPrinterServlet.parseJson(requestData, null); jsonSpec = OldAPIRequestConverter.convert(oldSpec, this.mapPrinter.getConfiguration()); // continue; } else { jsonSpec = MapPrinter.parseSpec(requestData); // continue; } testsRan++; String outputFormat = jsonSpec.getInternalObj().getString("outputFormat"); if (!ArrayUtils.contains(new String[]{"png", "jpg", "tiff"}, outputFormat)) { jsonSpec.getInternalObj().put("outputFormat", "png"); outputFormat = "png"; } ByteArrayOutputStream out = new ByteArrayOutputStream(); JSONObject headers = new JSONObject(); headers.append("Cookie", "examplesTestCookie=value"); headers.append("Referer", "http://localhost:8080/print"); headers.append("Host","localhost"); headers.append("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0"); headers.append("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); headers.append("Accept-Language", "en-US,en;q=0.5"); headers.append("Accept-Encoding", "gzip, deflate"); headers.append("Connection", "keep-alive"); headers.append("Cache-Control", "max-age=0"); JSONObject headersAttribute = new JSONObject(); headersAttribute.put(JSON_REQUEST_HEADERS, headers); jsonSpec.getJSONObject(JSON_ATTRIBUTES).getInternalObj().put(JSON_REQUEST_HEADERS, headersAttribute); this.mapPrinter.print(jsonSpec, out); BufferedImage image = ImageIO.read(new ByteArrayInputStream(out.toByteArray())); File expectedOutputDir = new File(example, "expected_output"); File expectedOutput = getExpectedOutput(outputFormat, requestFile, expectedOutputDir); if (!expectedOutput.exists()) { errors.put( example.getName() + " (" + requestFile.getName() + ")", new Exception("File not found: " + expectedOutput.toString())); continue; } int similarity = 50; File file = new File(expectedOutputDir, "image-similarity.txt"); if (file.isFile()) { String similarityString = Files.toString(file, Constants.DEFAULT_CHARSET); similarity = Integer.parseInt(similarityString.trim()); } new ImageSimilarity(image, 50).assertSimilarity(expectedOutput, similarity); } } catch (Throwable e) { errors.put(example.getName() + " (" + requestFile.getName() + ")", e); } } } catch (Throwable e) { errors.put(example.getName(), e); } return testsRan; } private File getExpectedOutput(String outputFormat, File requestFile, File expectedOutputDir) { File platformSpecificDir; if (System.getProperty("os.name").toLowerCase().contains("win")) { platformSpecificDir = new File(expectedOutputDir, "win"); } else if (System.getProperty("os.name").toLowerCase().contains("mac")) { platformSpecificDir = new File(expectedOutputDir, "mac"); } else { platformSpecificDir = new File(expectedOutputDir, "linux"); } final String imageName = requestFile.getName().replace(".json", "." + outputFormat); if (new File(platformSpecificDir, imageName).exists()) { return new File(platformSpecificDir, imageName); } return new File(expectedOutputDir, imageName); } private boolean hasRequestFile(File example) { for (File file : Files.fileTreeTraverser().children(example)) { if (isRequestDataFile(file)) { return true; } } return false; } private boolean isRequestDataFile(File requestFile) { return requestFile.getName().matches(REQUEST_DATA_FILE) || requestFile.getName().matches(OLD_API_REQUEST_DATA_FILE); } private static File getFile(Class<?> testClass, String fileName) { final URL resource = testClass.getResource(fileName); if (resource == null) { throw new AssertionError("Unable to find test resource: " + fileName); } return new File(resource.getFile()); } public static void main(String[] args) { JUnitCore junit = new JUnitCore(); if (args.length < 1) { System.err.println("This main is expected to have at least one parameter, it is a regular expression for selecting the examples to run"); System.exit(1); } if (args.length > 2) { System.err.println("A maximum of 2 parameters are allowed. param 1=example regex, param2 = configRegexp"); System.exit(1); } String filter = args[0]; if (args.length == 2) { filter += "/" + args[1]; } System.setProperty(FILTER_PROPERTY, filter); RunListener textListener = new TextListener(System.out); junit.addListener(textListener); junit.run(ExamplesTest.class); } }