/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.itest; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import org.diqube.itest.annotations.NeedsProcessPid; import org.diqube.itest.annotations.NeedsServer; import org.diqube.itest.annotations.NeedsTomcat; import org.diqube.itest.control.LogfileSaver; import org.diqube.itest.control.ServerClusterControl; import org.diqube.itest.control.ServerControl; import org.diqube.itest.control.TomcatControl; import org.diqube.itest.control.ToolControl; import org.diqube.itest.util.ProcessPidUtil; import org.diqube.itest.util.Unzip; import org.diqube.itest.util.Zip; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.SkipException; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import com.google.common.io.ByteStreams; /** * Abstract base class for all integration tests. * * @author Bastian Gloeckle */ public abstract class AbstractDiqubeIntegrationTest { private static final Logger logger = LoggerFactory.getLogger(AbstractDiqubeIntegrationTest.class); /** (required) local location of the jar of diqube-server */ private static final String PROP_SERVER_JAR = "diqube.itest.server.jar"; /** (required) local location of the jar of diqube-tool */ private static final String PROP_TOOL_JAR = "diqube.itest.tool.jar"; /** (required) local location of the war of diqube-ui */ private static final String PROP_UI_WAR = "diqube.itest.ui.war"; /** (required) local location of the zip containing a tomcat */ private static final String PROP_TOMCAT_ZIP = "diqube.itest.tomcat.zip"; /** (required) local directory where the tests can write files to */ private static final String PROP_WORK_DIR = "diqube.itest.work.dir"; /** * (optional) delete the {@link #testWorkDir}s of all the test methods. If set, set {@link #PROP_TARGET_LOG_DIR}, too! */ private static final String PROP_DELTE_TEST_DIRS = "diqube.itest.delete.test.work.dirs"; /** * (optional) before deleting the {@link #testWorkDir}s of test methods, save the logfiles. Collect all the logfiles * in this directory (these will be .zip files in child directories). */ private static final String PROP_TARGET_LOG_DIR = "diqube.itest.target.log.dir"; /** * System property name which needs the number of the server to be attached. If it is set, {@link ServerControl} will * not try to start a separate diqube-server, but expects one to be running. Please note that that server needs to * have to correct properties set (like dataDir, port etc.). * * This can be used to debug. */ public static final String PROP_SERVER_OVERRIDE = "diqube.itest.server.override."; private File serverJarFile; private File toolJarFile; private File uiWarFile; private File tomcatZipFile; private File classWorkDir; private File targetLogDir; private boolean deleteTestWorkDirs; /** A directory local to the test emthod where it can place some files etc. */ protected File testWorkDir = null; /** * The {@link TomcatControl} of a tomcat instance for the test method (only available if test method is annotated with * {@link NeedsTomcat}). */ protected TomcatControl tomcatControl = null; /** {@link ToolControl} for each test method. */ protected ToolControl toolControl; /** * contains the serverControls for the diqube-servers. Only available when test method is annotated with * {@link NeedsServer}. See also {@link #clusterControl}. */ protected List<ServerControl> serverControl = null; /** Controls a whole cluster of diqube-servers - combines the {@link #serverControl}s. */ protected ServerClusterControl clusterControl; public AbstractDiqubeIntegrationTest() { String serverJarFileName = System.getProperty(PROP_SERVER_JAR); String toolJarFileName = System.getProperty(PROP_TOOL_JAR); String uiWarFileName = System.getProperty(PROP_UI_WAR); String tomcatZipFileName = System.getProperty(PROP_TOMCAT_ZIP); String workDirName = System.getProperty(PROP_WORK_DIR); String targetLogDirName = System.getProperty(PROP_TARGET_LOG_DIR); deleteTestWorkDirs = System.getProperty(PROP_DELTE_TEST_DIRS) != null; if (serverJarFileName == null || toolJarFileName == null || uiWarFileName == null || tomcatZipFileName == null || workDirName == null) throw new RuntimeException("Not all system properties available."); serverJarFile = new File(serverJarFileName); toolJarFile = new File(toolJarFileName); uiWarFile = new File(uiWarFileName); tomcatZipFile = new File(tomcatZipFileName); targetLogDir = (targetLogDirName != null) ? new File(targetLogDirName) : null; File rootWorkDir = new File(workDirName); if (!serverJarFile.exists() || !serverJarFile.isFile() || !toolJarFile.exists() || !toolJarFile.isFile() || !uiWarFile.exists() || !uiWarFile.isFile() || !tomcatZipFile.exists() || !tomcatZipFile.isFile()) throw new RuntimeException("Not all input files present."); classWorkDir = new File(rootWorkDir, this.getClass().getSimpleName()); if (classWorkDir.exists()) deleteDirRecursively(classWorkDir.toPath()); ensureDirExists(classWorkDir); } @BeforeMethod public void initializeTestMethod(Method testMethod) { if (testMethod.isAnnotationPresent(NeedsProcessPid.class)) validatePidAvailable(testMethod); testWorkDir = new File(classWorkDir, testMethod.getName()); ensureDirExists(testWorkDir); if (testMethod.isAnnotationPresent(NeedsTomcat.class)) { new Unzip(tomcatZipFile).unzip(testWorkDir); tomcatControl = new TomcatControl(testWorkDir); } else tomcatControl = null; if (testMethod.isAnnotationPresent(NeedsServer.class)) { serverControl = new ArrayList<>(); NeedsServer annotation = testMethod.getAnnotation(NeedsServer.class); clusterControl = new ServerClusterControl(); for (int i = 0; i < annotation.servers(); i++) { boolean manualOverride = System.getProperty(PROP_SERVER_OVERRIDE + i) != null; if (manualOverride) { try { // We might just have deleted the workDir (from a previous run, workDirs are deleted in constructor). Give // the manual diqube-server here some time to recognize that the work dir was deleted (in order that it will // be able to re-attach the data dir to the new dir!) Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } File serverWorkDir = new File(testWorkDir, "server-" + i); ensureDirExists(serverWorkDir); serverControl .add(new ServerControl(serverJarFile, serverWorkDir, clusterControl, clusterControl, manualOverride)); } clusterControl.setServers(serverControl); if (!annotation.manualStart()) clusterControl.start(); } else { serverControl = null; clusterControl = null; } toolControl = new ToolControl(toolJarFile); } @AfterMethod public void cleanupTestMethod(Method testMethod) { if (tomcatControl != null && tomcatControl.isStarted()) tomcatControl.stop(); if (clusterControl != null) clusterControl.stop(); if (targetLogDir != null) { File logResultDir = new File(testWorkDir, "result_logs"); ensureDirExists(logResultDir); if (tomcatControl != null) saveLogs(tomcatControl, logResultDir, "tomcat"); for (int i = 0; i < serverControl.size(); i++) saveLogs(serverControl.get(i), logResultDir, "server-" + i); ensureDirExists(targetLogDir); Zip zip = new Zip(); File zipFile = new File(targetLogDir, this.getClass().getSimpleName() + "_" + testMethod.getName() + ".zip"); zip.zip(logResultDir, zipFile); logger.info("Collected logs and zipped them to '{}'", zipFile.getAbsolutePath()); } if (deleteTestWorkDirs) { logger.info("Removing work directory of the test."); deleteDirRecursively(testWorkDir.toPath()); } testWorkDir = null; tomcatControl = null; toolControl = null; serverControl = null; clusterControl = null; } /** * @throws SkipException * If we cannot identify PIDs on this system. */ private void validatePidAvailable(Method testMethod) throws SkipException { // start an arbitrary process and check if we can get the PID of it. ProcessBuilder pb = new ProcessBuilder("java", "-version"); try { Process p = pb.start(); ProcessPidUtil.getPid(p); p.destroyForcibly(); // if no exception: we found a PID, so we're fine. } catch (IOException | IllegalStateException e) { throw new SkipException("Skipping test method, because this system cannot identify the PID of sub-processes: " + testMethod.toString()); } } private void saveLogs(LogfileSaver logfileSaver, File logBaseDir, String subdir) { File logResultDir = new File(logBaseDir, subdir); ensureDirExists(logResultDir); logfileSaver.saveLogfiles(logResultDir); } private void ensureDirExists(File dir) { if (!dir.exists()) if (!dir.mkdirs()) throw new RuntimeException("Could not create directory " + dir.getAbsolutePath()); } protected void deleteDirRecursively(Path dir) { try { Files.walkFileTree(dir, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } catch (IOException e) { throw new RuntimeException("Could not clean work directory", e); } } /** * Fetches a file from the classpath, writes it to {@link #testWorkDir} and returns a {@link File} pointing to it. * * @param fileName * Name of the file on the classpath. */ protected File cp(String fileName) { InputStream is = this.getClass().getResourceAsStream(fileName); if (is == null) throw new RuntimeException("File not on classpath: " + fileName); if (fileName.startsWith("/")) fileName = fileName.substring(1); File targetFile = new File(new File(testWorkDir, "res"), fileName); ensureDirExists(targetFile.getParentFile()); try (FileOutputStream fos = new FileOutputStream(targetFile)) { ByteStreams.copy(is, fos); } catch (IOException e) { throw new RuntimeException("Could not write classpath file to HDD: " + fileName, e); } logger.info("Wrote '{}' to '{}'.", fileName, targetFile.getAbsolutePath()); return targetFile; } /** * Resolves a filename inside {@link #testWorkDir} * * @param fileName * Name of the file relative to {@link #testWorkDir}. */ protected File work(String fileName) { if (fileName.startsWith("/")) fileName = fileName.substring(1); return new File(testWorkDir, fileName); } }