diff --git a/src/main/java/org/jvnet/hudson/test/RunLoadCounter.java b/src/main/java/org/jvnet/hudson/test/RunLoadCounter.java index 0039004b3..02de9418b 100644 --- a/src/main/java/org/jvnet/hudson/test/RunLoadCounter.java +++ b/src/main/java/org/jvnet/hudson/test/RunLoadCounter.java @@ -25,17 +25,19 @@ package org.jvnet.hudson.test; import hudson.Extension; -import hudson.model.Action; +import hudson.ExtensionList; import hudson.model.InvisibleAction; +import hudson.model.Job; import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.model.listeners.RunListener; import java.io.IOException; -import java.util.Collection; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.RunAction2; -import jenkins.model.TransientActionFactory; import jenkins.model.lazy.LazyBuildMixIn; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -60,11 +62,12 @@ public static void prepare(LazyBuildMixIn.LazyLoadingJob project) throws I /** * Counts how many build records are loaded as a result of some task. - * @param project a project on which {@link #prepare} was called prior to creating builds + * @param project a project which may have some builds * @param thunk a task which is expected to load some build records * @return how many build records were actually {@linkplain Run#onLoad loaded} as a result */ public static int countLoads(LazyBuildMixIn.LazyLoadingJob project, Runnable thunk) { + MarkerAdder.register(project); project.getLazyBuildMixIn()._getRuns().purgeCache(); currProject = project; currCount = 0; @@ -81,7 +84,7 @@ public static int countLoads(LazyBuildMixIn.LazyLoadingJob project, Runnab /** * Asserts that at most a certain number of build records are loaded as a result of some task. - * @param project a project on which {@link #prepare} was called prior to creating builds + * @param project a project which may have some builds * @param max the maximum number of build records we expect to load * @param thunk a task which is expected to load some build records * @return the result of the task, if any @@ -91,6 +94,7 @@ public static int countLoads(LazyBuildMixIn.LazyLoadingJob project, Runnab */ public static T assertMaxLoads(LazyBuildMixIn.LazyLoadingJob project, int max, Callable thunk) throws Exception { + MarkerAdder.register(project); project.getLazyBuildMixIn()._getRuns().purgeCache(); currProject = project; currCount = 0; @@ -127,7 +131,9 @@ public void onLoad(Run run) { } @Override - public void onAttached(Run r) {} + public void onAttached(Run r) { + LOGGER.info(() -> "Registered on " + r); + } } /** @@ -135,16 +141,27 @@ public void onAttached(Run r) {} */ @Restricted(NoExternalUse.class) @Extension - public static final class MarkerAdder extends TransientActionFactory> { + public static final class MarkerAdder extends RunListener> { + private final Set jobs = new HashSet<>(); - @Override - public Class> type() { - return (Class) Run.class; + static void register(LazyBuildMixIn.LazyLoadingJob job) { + if (ExtensionList.lookupSingleton(MarkerAdder.class).jobs.add(((Job) job).getFullName())) { + for (Run build : job.getLazyBuildMixIn()._getRuns()) { + build.addAction(new Marker()); + try { + build.save(); + } catch (IOException x) { + throw new RuntimeException(x); + } + } + } } @Override - public Collection createFor(Run target) { - return Set.of(new Marker()); + public void onStarted(Run build, TaskListener listener) { + if (jobs.contains(build.getParent().getFullName())) { + build.addAction(new Marker()); + } } } } diff --git a/src/test/java/org/jvnet/hudson/test/RunLoadCounterTest.java b/src/test/java/org/jvnet/hudson/test/RunLoadCounterTest.java new file mode 100644 index 000000000..9c1cf8ad4 --- /dev/null +++ b/src/test/java/org/jvnet/hudson/test/RunLoadCounterTest.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright 2025 CloudBees, Inc. + * + * 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 org.jvnet.hudson.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.concurrent.Callable; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +@WithJenkins +public final class RunLoadCounterTest { + + @Test + void smokes(JenkinsRule r) throws Exception { + var p = r.createFreeStyleProject(); + for (int i = 1; i <= 10; i++) { + assertThat(r.buildAndAssertSuccess(p).number, is(i)); + } + assertThat( + RunLoadCounter.countLoads(p, () -> { + for (int i = 1; i <= 9; i += 2) { + assertThat(p.getBuildByNumber(i).number, is(i)); + } + }), + is(5)); + Callable twoLoads = () -> { + for (int i = 1; i <= 6; i += 5) { + assertThat(p.getBuildByNumber(i).number, is(i)); + } + return true; + }; + assertThat(RunLoadCounter.assertMaxLoads(p, 2, twoLoads), is(true)); + assertThat(RunLoadCounter.assertMaxLoads(p, 3, twoLoads), is(true)); + assertThrows(AssertionError.class, () -> RunLoadCounter.assertMaxLoads(p, 1, twoLoads)); + for (int i = 11; i <= 20; i++) { + assertThat(r.buildAndAssertSuccess(p).number, is(i)); + } + assertThat( + RunLoadCounter.countLoads(p, () -> { + for (int i = 1; i <= 19; i += 2) { + assertThat(p.getBuildByNumber(i).number, is(i)); + } + }), + is(10)); + assertThat(RunLoadCounter.assertMaxLoads(p, 2, twoLoads), is(true)); + assertThat(RunLoadCounter.assertMaxLoads(p, 3, twoLoads), is(true)); + assertThrows(AssertionError.class, () -> RunLoadCounter.assertMaxLoads(p, 1, twoLoads)); + } +}