Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 75 additions & 22 deletions src/main/java/org/jvnet/hudson/test/RestartableJenkinsRule.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.jvnet.hudson.test;

import groovy.lang.Closure;
import hudson.FilePath;
import org.junit.Assert;
import org.junit.rules.MethodRule;
import org.junit.rules.TemporaryFolder;
Expand All @@ -10,13 +9,22 @@
import org.junit.runners.model.Statement;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Provides a pattern for executing a sequence of steps.
Expand Down Expand Up @@ -55,6 +63,7 @@ public class RestartableJenkinsRule implements MethodRule {
*/
public File home;

private static final Logger LOGGER = Logger.getLogger(HudsonTestCase.class.getName());

@Override
public Statement apply(final Statement base, FrameworkMethod method, Object target) {
Expand Down Expand Up @@ -86,6 +95,59 @@ public void evaluate() throws Throwable {
});
}

/** Approach adapted from https://stackoverflow.com/questions/6214703/copy-entire-directory-contents-to-another-directory */
static class CopyFileVisitor extends SimpleFileVisitor<Path> {
private final Path targetPath;
private Path sourcePath = null;
public CopyFileVisitor(Path targetPath) {
this.targetPath = targetPath;
}

@Override
public FileVisitResult preVisitDirectory(final Path dir,
final BasicFileAttributes attrs) throws IOException {
if (sourcePath == null) {
sourcePath = dir;
} else {
Files.createDirectories(targetPath.resolve(sourcePath
.relativize(dir)));
}

return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(final Path file,
final BasicFileAttributes attrs) throws IOException {
try {
if (!Files.isSymbolicLink(file)) {
// Needed because Jenkins includes invalid lastSuccessful symlinks and otherwise we get a NoSuchFileException
Files.copy(file,
targetPath.resolve(sourcePath.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES);
} else if (Files.isSymbolicLink(file) && Files.exists(Files.readSymbolicLink(file))) {
Files.copy(file,
targetPath.resolve(sourcePath.relativize(file)), LinkOption.NOFOLLOW_LINKS, StandardCopyOption.COPY_ATTRIBUTES);
}
} catch (NoSuchFileException nsfe) {
// File removed in between scan beginning and when we try to copy it, ignore it
LOGGER.log(Level.FINE, "File disappeared while trying to copy to new home, continuing anyway: "+file.toString());
}

return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
if (exc instanceof FileNotFoundException) {
LOGGER.log(Level.FINE, "File not found while trying to copy to new home, continuing anyway: "+file.toString());
return FileVisitResult.CONTINUE;
} else {
LOGGER.log(Level.WARNING, "Error copying file", exc);
return FileVisitResult.TERMINATE;
}
}
}

/**
* Simulate an abrupt failure of Jenkins to see if it appropriately handles inconsistent states when
* shutdown cleanup is not performed or data is not written fully to disk.
Expand All @@ -98,25 +160,16 @@ public void evaluate() throws Throwable {
* @throws IOException
*/
void simulateAbruptShutdown() throws IOException {
File homeDir = this.home;
TemporaryFolder temp = new TemporaryFolder();
temp.create();
File newHome = temp.newFolder();

// Copy efficiently
try {
try {
new FilePath(homeDir).copyRecursiveTo(new FilePath(newHome));
} catch (NoSuchFileException nsfe) {
// Retry in case of tempfile deletion while copying.
newHome.delete();
newHome = temp.newFolder();
new FilePath(homeDir).copyRecursiveTo(new FilePath(newHome));
}
} catch (InterruptedException ie) {
throw new IOException(ie);
}
home = newHome;
LOGGER.log(Level.INFO, "Beginning snapshot of JENKINS_HOME so we can simulate abrupt shutdown. Disk writes MAY be lost if they happen after this.");
File homeDir = this.home;
TemporaryFolder temp = new TemporaryFolder();
temp.create();
File newHome = temp.newFolder();

// Copy efficiently
Files.walkFileTree(homeDir.toPath(), Collections.EMPTY_SET, 99, new CopyFileVisitor(newHome.toPath()));
LOGGER.log(Level.INFO, "Finished snapshot of JENKINS_HOME, any disk writes by Jenkins after this are lost as we will simulate suddenly killing the Jenkins process and switch to the snapshot.");
home = newHome;
}

/**
Expand Down