未验证 提交 ece68cdd 编写于 作者: J Jesse Glick 提交者: GitHub

Merge pull request #3982 from jglick/build-symlink-JENKINS-37862

[JENKINS-37862] Removing build symlink functionality
......@@ -27,6 +27,7 @@ import jenkins.model.PeepholePermalink;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.CheckForNull;
/**
* Optional interface for {@link Action}s that are attached
......@@ -86,7 +87,7 @@ public interface PermalinkProjectAction extends Action {
* @return null
* if the target of the permalink doesn't exist.
*/
public abstract Run<?,?> resolve(Job<?,?> job);
public abstract @CheckForNull Run<?,?> resolve(Job<?,?> job);
/**
* List of {@link Permalink}s that are built into Jenkins.
......
......@@ -107,7 +107,6 @@ import jenkins.model.ArtifactManagerFactory;
import jenkins.model.BuildDiscarder;
import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.model.PeepholePermalink;
import jenkins.model.RunAction2;
import jenkins.model.StandardArtifactManager;
import jenkins.model.lazy.BuildReference;
......@@ -1813,8 +1812,6 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
RunListener.fireStarted(this,listener);
updateSymlinks(listener);
setResult(job.run(listener));
LOGGER.log(INFO, "{0} main build action completed: {1}", new Object[] {this, result});
......@@ -1926,36 +1923,10 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
}
/**
* Makes sure that {@code lastSuccessful} and {@code lastStable} legacy links in the project’s root directory exist.
* Normally you do not need to call this explicitly, since {@link #execute} does so,
* but this may be needed if you are creating synthetic {@link Run}s as part of a container project (such as Maven builds in a module set).
* You should also ensure that {@link RunListener#fireStarted} and {@link RunListener#fireCompleted} are called.
* @param listener probably unused
* @throws InterruptedException probably not thrown
* @since 1.530
* @deprecated After JENKINS-37862 this no longer does anything.
*/
public final void updateSymlinks(@Nonnull TaskListener listener) throws InterruptedException {
createSymlink(listener, "lastSuccessful", PermalinkProjectAction.Permalink.LAST_SUCCESSFUL_BUILD);
createSymlink(listener, "lastStable", PermalinkProjectAction.Permalink.LAST_STABLE_BUILD);
}
/**
* Backward compatibility.
*
* We used to have $JENKINS_HOME/jobs/JOBNAME/lastStable and lastSuccessful symlinked to the appropriate
* builds, but now those are done in {@link PeepholePermalink}. So here, we simply create symlinks that
* resolves to the symlink created by {@link PeepholePermalink}.
*/
private void createSymlink(@Nonnull TaskListener listener, @Nonnull String name, @Nonnull PermalinkProjectAction.Permalink target) throws InterruptedException {
File buildDir = getParent().getBuildDir();
File rootDir = getParent().getRootDir();
String targetDir;
if (buildDir.equals(new File(rootDir, "builds"))) {
targetDir = "builds" + File.separator + target.getId();
} else {
targetDir = buildDir + File.separator + target.getId();
}
Util.createSymlink(rootDir, targetDir, name, listener);
}
@Deprecated
public final void updateSymlinks(@Nonnull TaskListener listener) throws InterruptedException {}
/**
* Handles a fatal build problem (exception) that occurred during the build.
......
......@@ -9,18 +9,17 @@ import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.util.AtomicFileWriter;
import hudson.util.StreamTaskListener;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
/**
* Convenient base implementation for {@link Permalink}s that satisfy
......@@ -46,8 +45,7 @@ import org.apache.commons.io.FileUtils;
*
* <p>
* This base class provides a file-based caching mechanism that avoids
* walking the long build history. The cache is a symlink to the build directory
* where symlinks are supported, and text file that contains the build number otherwise.
* walking the long build history.
*
* <p>
* The implementation transparently tolerates G(B) that goes from true to false over time
......@@ -56,13 +54,17 @@ import org.apache.commons.io.FileUtils;
* from false to true, then call {@link #resolve(Job)} to check the current permalink target
* is up to date, then call {@link #updateCache(Job, Run)} if it needs updating.
*
* @author Kohsuke Kawaguchi
* @since 1.507
*/
public abstract class PeepholePermalink extends Permalink implements Predicate<Run<?,?>> {
/** JENKINS-22822: avoids rereading symlinks */
static final Map<File,String> symlinks = new HashMap<>();
/**
* JENKINS-22822: avoids rereading caches.
* Top map keys are {@link builds} directories.
* Inner maps are from permalink name to build number.
* Synchronization is first on the outer map, then on the inner.
*/
private static final Map<File, Map<String, Integer>> caches = new HashMap<>();
/**
* Checks if the given build satisfies the peep-hole criteria.
......@@ -71,9 +73,8 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
*/
public abstract boolean apply(Run<?,?> run);
/**
* The file in which the permalink target gets recorded.
*/
/** @deprecated No longer used. */
@Deprecated
protected File getPermalinkFile(Job<?,?> job) {
return new File(job.getBuildDir(),getId());
}
......@@ -83,32 +84,27 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
*/
@Override
public Run<?, ?> resolve(Job<?, ?> job) {
File f = getPermalinkFile(job);
Run<?,?> b=null;
try {
String target = readSymlink(f);
if (target!=null) {
int n = Integer.parseInt(Util.getFileName(target));
if (n==RESOLVES_TO_NONE) return null;
b = job.getBuildByNumber(n);
if (b!=null && apply(b))
return b; // found it (in the most efficient way possible)
// the cache is stale. start the search
if (b==null)
b=job.getNearestOldBuild(n);
Map<String, Integer> cache = cacheFor(job.getBuildDir());
int n;
synchronized (cache) {
n = cache.getOrDefault(getId(), 0);
}
if (n == RESOLVES_TO_NONE) {
return null;
}
Run<?, ?> b;
if (n > 0) {
b = job.getBuildByNumber(n);
if (b != null && apply(b)) {
return b; // found it (in the most efficient way possible)
}
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Failed to read permalink cache:" + f, e);
// if we fail to read the cache, fall back to the re-computation
} catch (NumberFormatException e) {
LOGGER.log(Level.WARNING, "Failed to parse the build number in the permalink cache:" + f, e);
// if we fail to read the cache, fall back to the re-computation
} catch (IOException e) {
// this happens when the symlink doesn't exist
// (and it cannot be distinguished from the case when the actual I/O error happened
} else {
b = null;
}
// the cache is stale. start the search
if (b == null) {
b = job.getNearestOldBuild(n);
}
if (b==null) {
......@@ -133,72 +129,71 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
return b;
}
/**
* Remembers the value 'n' in the cache for future {@link #resolve(Job)}.
*/
protected void updateCache(@Nonnull Job<?,?> job, @Nullable Run<?,?> b) {
final int n = b==null ? RESOLVES_TO_NONE : b.getNumber();
File cache = getPermalinkFile(job);
cache.getParentFile().mkdirs();
try {
String target = String.valueOf(n);
if (b != null && !new File(job.getBuildDir(), target).exists()) {
// (re)create the build Number->Id symlink
Util.createSymlink(job.getBuildDir(),b.getId(),target,TaskListener.NULL);
private static @Nonnull Map<String, Integer> cacheFor(@Nonnull File buildDir) {
synchronized (caches) {
Map<String, Integer> cache = caches.get(buildDir);
if (cache == null) {
cache = load(buildDir);
caches.put(buildDir, cache);
}
writeSymlink(cache, target);
} catch (IOException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Failed to update "+job+" "+getId()+" permalink for " + b, e);
cache.delete();
return cache;
}
}
// File.exists returns false for a link with a missing target, so for Java 6 compatibility we have to use this circuitous method to see if it was created.
private static boolean exists(File link) {
File[] kids = link.getParentFile().listFiles();
return kids != null && Arrays.asList(kids).contains(link);
}
static String readSymlink(File cache) throws IOException, InterruptedException {
synchronized (symlinks) {
String target = symlinks.get(cache);
if (target != null) {
LOGGER.log(Level.FINE, "readSymlink cached {0} → {1}", new Object[] {cache, target});
return target;
private static @Nonnull Map<String, Integer> load(@Nonnull File buildDir) {
Map<String, Integer> cache = new TreeMap<>();
File storage = storageFor(buildDir);
if (storage.isFile()) {
try {
Files.lines(storage.toPath(), StandardCharsets.UTF_8).forEach(line -> {
int idx = line.indexOf(' ');
if (idx == -1) {
return;
}
try {
cache.put(line.substring(0, idx), Integer.parseInt(line.substring(idx + 1)));
} catch (NumberFormatException x) {
LOGGER.log(Level.WARNING, "failed to read " + storage, x);
}
});
} catch (IOException x) {
LOGGER.log(Level.WARNING, "failed to read " + storage, x);
}
LOGGER.fine(() -> "loading from " + storage + ": " + cache);
}
String target = Util.resolveSymlink(cache);
if (target==null && cache.exists()) {
// if this file isn't a symlink, it must be a regular file
target = FileUtils.readFileToString(cache,"UTF-8").trim();
}
LOGGER.log(Level.FINE, "readSymlink {0} → {1}", new Object[] {cache, target});
synchronized (symlinks) {
symlinks.put(cache, target);
}
return target;
return cache;
}
static void writeSymlink(File cache, String target) throws IOException, InterruptedException {
LOGGER.log(Level.FINE, "writeSymlink {0} → {1}", new Object[] {cache, target});
synchronized (symlinks) {
symlinks.put(cache, target);
}
StringWriter w = new StringWriter();
StreamTaskListener listener = new StreamTaskListener(w);
Util.createSymlink(cache.getParentFile(),target,cache.getName(),listener);
// Avoid calling resolveSymlink on a nonexistent file as it will probably throw an IOException:
if (!exists(cache) || Util.resolveSymlink(cache)==null) {
// symlink not supported. use a regular file
AtomicFileWriter cw = new AtomicFileWriter(cache);
try {
cw.write(target);
cw.commit();
} finally {
cw.abort();
}
static @Nonnull File storageFor(@Nonnull File buildDir) {
return new File(buildDir, "permalinks");
}
/**
* Remembers the value 'n' in the cache for future {@link #resolve(Job)}.
*/
protected void updateCache(@Nonnull Job<?,?> job, @CheckForNull Run<?,?> b) {
File buildDir = job.getBuildDir();
Map<String, Integer> cache = cacheFor(buildDir);
synchronized (cache) {
cache.put(getId(), b == null ? RESOLVES_TO_NONE : b.getNumber());
File storage = storageFor(buildDir);
LOGGER.fine(() -> "saving to " + storage + ": " + cache);
try {
AtomicFileWriter cw = new AtomicFileWriter(storage);
try {
for (Map.Entry<String, Integer> entry : cache.entrySet()) {
cw.write(entry.getKey());
cw.write(' ');
cw.write(Integer.toString(entry.getValue()));
cw.write('\n');
}
cw.commit();
} finally {
cw.abort();
}
} catch (IOException x) {
LOGGER.log(Level.WARNING, "failed to update " + storage, x);
}
}
}
......@@ -213,8 +208,7 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
for (PeepholePermalink pp : Util.filter(j.getPermalinks(), PeepholePermalink.class)) {
if (pp.resolve(j)==run) {
Run<?,?> r = pp.find(run.getPreviousBuild());
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Updating "+pp.getPermalinkFile(j).getName()+" permalink from deleted "+run.getNumber()+" to "+(r == null ? -1 : r.getNumber()));
LOGGER.fine(() -> "Updating " + pp.getId() + " permalink from deleted " + run + " to " + (r == null ? -1 : r.getNumber()));
pp.updateCache(j,r);
}
}
......@@ -230,8 +224,7 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
if (pp.apply(run)) {
Run<?, ?> cur = pp.resolve(j);
if (cur==null || cur.getNumber()<run.getNumber()) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Updating "+pp.getPermalinkFile(j).getName()+" permalink to completed "+run.getNumber());
LOGGER.fine(() -> "Updating " + pp.getId() + " permalink to completed " + run);
pp.updateCache(j,run);
}
}
......
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.model;
import java.io.File;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
public class PeepholePermalinkTest {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
@Issue("JENKINS-17681")
@Test public void symlinks() throws Exception {
File link = new File(tmp.getRoot(), "link");
PeepholePermalink.writeSymlink(link, "stuff");
PeepholePermalink.symlinks.clear(); // so we actually test the filesystem
assertEquals("stuff", PeepholePermalink.readSymlink(link));
}
}
......@@ -34,13 +34,11 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.maven.MavenModuleSet;
import hudson.scm.NullSCM;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.tasks.ArtifactArchiver;
import hudson.tasks.BatchFile;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Shell;
......@@ -50,8 +48,6 @@ import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.OneShotEvent;
import hudson.util.StreamTaskListener;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
......@@ -60,11 +56,9 @@ import java.util.ResourceBundle;
import java.util.Vector;
import java.util.concurrent.Future;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
......@@ -258,65 +252,6 @@ public class AbstractProjectTest {
}
}
@Test
@Issue("JENKINS-1986")
public void buildSymlinks() throws Exception {
Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
FreeStyleProject job = j.createFreeStyleProject();
job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n"));
FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get();
File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"),
lastStable = new File(job.getRootDir(), "lastStable");
// First build creates links
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
FreeStyleBuild build2 = job.scheduleBuild2(0, new Cause.UserCause()).get();
// Another build updates links
assertSymlinkForBuild(lastSuccessful, 2);
assertSymlinkForBuild(lastStable, 2);
// Delete latest build should update links
build2.delete();
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
// Delete all builds should remove links
build.delete();
assertFalse("lastSuccessful link should be removed", lastSuccessful.exists());
assertFalse("lastStable link should be removed", lastStable.exists());
}
private static void assertSymlinkForBuild(File file, int buildNumber)
throws IOException, InterruptedException {
assert file.exists() : "should exist and point to something that exists";
assert Util.isSymlink(file) : "should be symlink";
String s = FileUtils.readFileToString(new File(file, "log"));
assert s.contains("Build #" + buildNumber + "\n") : "link should point to build #$buildNumber, but link was: ${Util.resolveSymlink(file, TaskListener.NULL)}\nand log was:\n$s";
}
@Test
@Issue("JENKINS-2543")
public void symlinkForPostBuildFailure() throws Exception {
Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
// Links should be updated after post-build actions when final build result is known
FreeStyleProject job = j.createFreeStyleProject();
job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n"));
FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get();
assert Result.SUCCESS == build.getResult();
File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"),
lastStable = new File(job.getRootDir(), "lastStable");
// First build creates links
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
// Archive artifacts that don't exist to create failure in post-build action
job.getPublishersList().add(new ArtifactArchiver("*.foo", "", false, false));
build = job.scheduleBuild2(0, new Cause.UserCause()).get();
assert Result.FAILURE == build.getResult();
// Links should not be updated since build failed
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
}
/* TODO too slow, seems capable of causing testWorkspaceLock to time out:
@Test
@Issue("JENKINS-15156")
......
package jenkins.model;
import hudson.Functions;
import hudson.Util;
import hudson.init.InitMilestone;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
......@@ -21,12 +20,10 @@ import org.jvnet.hudson.test.RestartableJenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.stream.Stream;
import java.util.stream.Collectors;
......@@ -353,45 +350,4 @@ public class JenkinsBuildsAndWorkspacesDirectoriesTest {
});
}
@Test
@Issue("JENKINS-17137")
public void externalBuildDirectorySymlinks() throws Exception {
assumeFalse(Functions.isWindows()); // symlinks may not be available
// Hack to get String builds usable in lambda below
final List<String> builds = new ArrayList<>();
story.then(steps -> {
builds.add(story.j.createTmpDir().toString());
setBuildsDirProperty(builds.get(0) + "/${ITEM_FULL_NAME}");
});
story.then(steps -> {
assertEquals(builds.get(0) + "/${ITEM_FULL_NAME}", story.j.jenkins.getRawBuildsDir());
FreeStyleProject p = story.j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "p");
FreeStyleBuild b1 = p.scheduleBuild2(0).get();
File link = new File(p.getRootDir(), "lastStable");
assertTrue(link.exists());
assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath());
FreeStyleBuild b2 = p.scheduleBuild2(0).get();
assertTrue(link.exists());
assertEquals(resolveAll(link).getAbsolutePath(), b2.getRootDir().getAbsolutePath());
b2.delete();
assertTrue(link.exists());
assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath());
b1.delete();
assertFalse(link.exists());
});
}
private File resolveAll(File link) throws InterruptedException, IOException {
while (true) {
File f = Util.resolveSymlinkToFile(link);
if (f == null) {
return link;
}
link = f;
}
}
}
package jenkins.model;
import hudson.Functions;
import hudson.Util;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Job;
import hudson.model.Run;
import java.io.File;
import java.nio.file.Files;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.FailureBuilder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
public class PeepholePermalinkTest {
......@@ -24,76 +22,45 @@ public class PeepholePermalinkTest {
*/
@Test
public void basics() throws Exception {
Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
File lsb = new File(p.getBuildDir(), "lastSuccessfulBuild");
File lfb = new File(p.getBuildDir(), "lastFailedBuild");
String lsb = "lastSuccessfulBuild";
String lfb = "lastFailedBuild";
assertLink(lsb, b1);
assertStorage(lsb, p, b1);
// now another build that fails
p.getBuildersList().add(new FailureBuilder());
FreeStyleBuild b2 = p.scheduleBuild2(0).get();
assertLink(lsb, b1);
assertLink(lfb, b2);
assertStorage(lsb, p, b1);
assertStorage(lfb, p, b2);
// one more build and this time it succeeds
p.getBuildersList().clear();
FreeStyleBuild b3 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
assertLink(lsb, b3);
assertLink(lfb, b2);
assertStorage(lsb, p, b3);
assertStorage(lfb, p, b2);
// delete b3 and symlinks should update properly
// delete b3 and links should update properly
b3.delete();
assertLink(lsb, b1);
assertLink(lfb, b2);
assertStorage(lsb, p, b1);
assertStorage(lfb, p, b2);
b1.delete();
assertLink(lsb, null);
assertLink(lfb, b2);
assertStorage(lsb, p, null);
assertStorage(lfb, p, b2);
b2.delete();
assertLink(lsb, null);
assertLink(lfb, null);
}
private void assertLink(File symlink, Run build) throws Exception {
assertEquals(build == null ? "-1" : Integer.toString(build.getNumber()), Util.resolveSymlink(symlink));
}
/**
* job/JOBNAME/lastStable and job/JOBNAME/lastSuccessful symlinks that we
* used to generate should still work
*/
@Test
public void legacyCompatibility() throws Exception {
Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
for (String n : new String[] {"lastStable", "lastSuccessful"}) {
// test if they both point to b1
assertEquals(new File(p.getRootDir(), n + "/build.xml").length(), new File(b1.getRootDir(), "build.xml").length());
}
assertStorage(lsb, p, null);
assertStorage(lfb, p, null);
}
@Test
@Issue("JENKINS-19034")
public void rebuildBuildNumberPermalinks() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
File f = new File(p.getBuildDir(), "1");
// assertTrue(Util.isSymlink(f))
f.delete();
PeepholePermalink link = (PeepholePermalink) p.getPermalinks().stream().filter(l -> l instanceof PeepholePermalink).findAny().get();
link.updateCache(p, b);
assertTrue("build symlink hasn't been restored", f.exists());
private void assertStorage(String id, Job<?, ?> job, Run<?, ?> build) throws Exception {
assertThat(Files.readAllLines(PeepholePermalink.storageFor(job.getBuildDir()).toPath()),
hasItem(id + " " + (build == null ? -1 : build.getNumber())));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册