/* * RHQ Management Platform * Copyright (C) 2011 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.core.pc.drift; import static java.util.Arrays.asList; import static org.apache.commons.io.FileUtils.deleteDirectory; import static org.apache.commons.io.IOUtils.write; import static org.apache.commons.io.IOUtils.writeLines; import static org.rhq.core.domain.drift.DriftConfigurationDefinition.BaseDirValueContext.fileSystem; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Method; import java.util.Random; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.rhq.common.drift.Headers; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.drift.DriftChangeSetCategory; import org.rhq.core.domain.drift.DriftDefinition; import org.rhq.core.util.MessageDigestGenerator; /** * A base test class that provides a framework for drift related tests. DriftTest sets up * directories for change sets and for fake resources. Before each test method is run, a * uniquely named resource directory is created. Files to be included in a change set for * example can be placed under this directory. DriftTest also provides a number of helper * methods for things like generating a SHA-256 hash, accessing a change set file, and * obtaining a unique resource id. * <br/> * <br/> * DriftTest writes all output to a directory named after the test class name. Suppose your * test class name is MyDriftTest. DriftTest creates the following directories: * * <ul> * <li><b>target/MyDriftTest</b> - the base directory to which all output will be written</li> * <li><b>target/MyDriftTest/resources</b> - directory in which fake resources are created for * each test method.</li> * <li><b>target/MyDriftTest/changesets</b> - directory to which change set files are written</li> * </ul> */ public class DriftTest { /** * The base directory to which change sets are written to and read from. This directory * is deleted and recreated before any test methods are run. It is not deleted after * individual test methods are run so that output is available for inspection after tests * run. */ protected File changeSetsDir; /** * The base directory to which resources are written/stored. This directory is deleted * and recreated before any test methods are run. It is not deleted after individual * test methods are run so that output is available for inspection after tests run. */ protected File resourcesDir; /** * A {@link ChangeSetManager} to use in tests for reading, writing, and finding change * sets. */ protected ChangeSetManager changeSetMgr; /** * This is basically a counter used to generate unique resource ids across test methods. * The current id is obtained from {@link #resourceId()}. The next (or a new) id is * obtained from {@link #nextResourceId()}. */ private int resourceId; /** * Resource files for a given tests are to be written to this directory (or * subdirectories). This directory is created before each test method runs. Its name is * of the form: * <br/> * <pre> * <test_method_name>-id * </pre> * where test_method_name is the name of the current test method, and id is unique * integer obtained from {@link #nextResourceId()}. */ protected File resourceDir; /** * The default interval assigned to drift definitions created using * {@link #driftDefinition(String, String)} */ protected long defaultInterval = 1800L; // 30 minutes; private MessageDigestGenerator digestGenerator; /** * Used for creating random files in {@link #createRandomFile(java.io.File, String)} */ private Random random = new Random(); /** * Deletes the base output directory (which is the name of the test class), removing * output from a previous run. The output directories (i.e., change sets and resources) * are then recreated. * * @throws Exception */ @BeforeClass public void initResourcesAndChangeSetsDirs() throws Exception { File basedir = basedir(); deleteDirectory(basedir); basedir.mkdir(); changeSetsDir = mkdir(basedir, "changesets"); resourcesDir = mkdir(basedir, "resources"); digestGenerator = new MessageDigestGenerator(MessageDigestGenerator.SHA_256); } /** * Generates a uniquely named resource directory for the test method about to run. The * directory name is <test_method_name>-<id> where id is an integer id * generated from {@link #nextResourceId()}. The member variable, {@link #resourceDir}, * is initialized to this directory and is accessible to subclasses. * * @param test */ @BeforeMethod public void setUp(Method test) { resourceDir = mkdir(resourcesDir, test.getName() + "-" + nextResourceId()); changeSetMgr = new ChangeSetManagerImpl(changeSetsDir); } protected File basedir() { return new File("target", getClass().getSimpleName()); } /** @return The current or last resource id generated */ protected int resourceId() { return resourceId; } /** @return Generates and returns the next resource id */ protected int nextResourceId() { return ++resourceId; } /** * Creates and returns the specified directory. Any nonexistent parent directories are * created as well. * * @param parent The parent directory * @param name The name of the directory to be created * @return The directory */ protected File mkdir(File parent, String name) { File dir = new File(parent, name); dir.mkdirs(); return dir; } /** * Returns the change set file for the specified drift definition for the resource * with the id that can be obtained from {@link #resourceId}. The type argument * determines whether a coverage or drift change set file is returned. * * @param config The drift definition name * @param type Determines whether a coverage or drift change set file is to be returned * @return The change set file * @throws IOException */ protected File changeSet(String config, DriftChangeSetCategory type) throws IOException { return changeSetMgr.findChangeSet(resourceId(), config, type); } protected File pinnedSnapshot(String definitionName) throws Exception { return new File(changeSetDir(definitionName), "snapshot.pinned"); } /** * Returns the previous version snapshot file. This file is generated when an initial * snapshot has already been generated and drift is subsequently detected. * * @param driftDefinitionName The drift definition name * @return The previous version snapshot file * @throws Exception */ protected File previousSnapshot(String driftDefinitionName) throws Exception { File driftDefDir = changeSetDir(driftDefinitionName); return new File(driftDefDir, "changeset.txt.previous"); } protected File changeSetDir(String driftDefName) throws Exception { File dir = new File(new File(changeSetsDir, Integer.toString(resourceId)), driftDefName); dir.mkdirs(); return dir; } /** * Generates a SHA-256 hash * @param file The file for which the hash will be generated * @return The SHA-256 hash as a string * @throws IOException */ protected String sha256(File file) { try { return digestGenerator.calcDigestString(file); } catch (IOException e) { throw new RuntimeException("Failed to calculate SHA-256 hash for " + file.getPath(), e); } } protected File createRandomFile(File dir, String fileName) throws Exception { return createRandomFile(dir, fileName, 32); } protected File createRandomFile(File dir, String fileName, int numBytes) throws Exception { File file = new File(dir, fileName); FileOutputStream stream = new FileOutputStream(file); byte[] bytes = new byte[numBytes]; random.nextBytes(bytes); write(bytes, stream); stream.close(); return file; } protected void writeChangeSet(File changeSetDir, String... changeSet) throws Exception { File changeSetFile = new File(changeSetDir, "changeset.txt"); BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(changeSetFile)); writeLines(asList(changeSet), "\n", stream); stream.close(); } /** * Creates a {@link DriftDefinition} with the specified basedir. The file system is * used as the context for the basedir which means the path specified is used as is. * The interval property is set to {@link #defaultInterval}. * * @param name The definition name * @param basedir An absolute path of the base directory * @return The drift definition object */ protected DriftDefinition driftDefinition(String name, String basedir) { DriftDefinition config = new DriftDefinition(new Configuration()); config.setName(name); config.setBasedir(new DriftDefinition.BaseDirectory(fileSystem, basedir)); config.setEnabled(true); config.setInterval(defaultInterval); return config; } protected Headers createHeaders(DriftDefinition driftDef, DriftChangeSetCategory type) { return createHeaders(driftDef, type, 0); } protected Headers createHeaders(DriftDefinition driftDef, DriftChangeSetCategory type, int version) { Headers headers = new Headers(); headers.setResourceId(resourceId()); headers.setDriftDefinitionId(driftDef.getId()); headers.setDriftDefinitionName(driftDef.getName()); headers.setBasedir(resourceDir.getAbsolutePath()); headers.setType(type); headers.setVersion(version); return headers; } }