Skip to content

Commit 93b0b9f

Browse files
authored
test: Add inversePrecedence regression tests (#1013)
* test: Add inversePrecedence regression tests for #861 and #864 Add two new tests to LockStepTest: - lockInverseOrderWithLabel: verifies inversePrecedence=true grants the lock to the newest build when locking by label (JENKINS-40787, #861) - lockInverseOrderMixedDifferentJobs: verifies each waiting job's own inversePrecedence flag controls queue ordering when using separate pipeline jobs with mixed settings (JENKINS-41070, #864) * refactor: Move inversePrecedence tests to separate test class Extract lockInverseOrderWithLabel and lockInverseOrderMixedDifferentJobs into LockStepInversePrecedenceTest to keep LockStepTest smaller and avoid CI timeouts. * Disable lockInverseOrderWithLabel test (blocked by #861)
1 parent 67b070c commit 93b0b9f

1 file changed

Lines changed: 170 additions & 0 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package org.jenkins.plugins.lockableresources;
2+
3+
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
4+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
5+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
6+
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
7+
import org.junit.jupiter.api.Disabled;
8+
import org.junit.jupiter.api.Test;
9+
import org.jvnet.hudson.test.Issue;
10+
import org.jvnet.hudson.test.JenkinsRule;
11+
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
12+
13+
/**
14+
* Tests for inversePrecedence queue ordering (issues #861 and #864).
15+
*
16+
* <p>Extracted from {@link LockStepTest} to keep test classes small and avoid CI timeouts.
17+
*/
18+
@WithJenkins
19+
class LockStepInversePrecedenceTest extends LockStepTestBase {
20+
21+
/**
22+
* Verify that inversePrecedence=true grants the lock to the newest build
23+
* when locking by <b>label</b> (not named resource).
24+
*
25+
* <pre>
26+
* start time | build | label | inversePrecedence
27+
* -----------|-------|--------|-------------------
28+
* 00:01 | b1 | label1 | true (acquires)
29+
* 00:02 | b2 | label1 | true (waits)
30+
* 00:03 | b3 | label1 | true (waits)
31+
*
32+
* expected lock order: b1 -> b3 -> b2
33+
* </pre>
34+
*/
35+
@Test
36+
@Issue({"JENKINS-40787", "GITHUB-861"})
37+
@Disabled("Blocked by #861 — inversePrecedence is not applied for label-based locks, test hangs")
38+
void lockInverseOrderWithLabel(JenkinsRule j) throws Exception {
39+
LockableResourcesManager.get().createResourceWithLabel("resource1", "label1");
40+
WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p");
41+
p.setDefinition(new CpsFlowDefinition("""
42+
lock(label: 'label1', inversePrecedence: true) {
43+
semaphore 'wait-inside'
44+
}
45+
echo 'Finish'""", true));
46+
47+
WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
48+
SemaphoreStep.waitForStart("wait-inside/1", b1);
49+
50+
WorkflowRun b2 = p.scheduleBuild2(0).waitForStart();
51+
j.waitForMessage("[Label: label1] is locked by build " + b1.getFullDisplayName(), b2);
52+
isPaused(b2, 1, 1);
53+
54+
WorkflowRun b3 = p.scheduleBuild2(0).waitForStart();
55+
j.waitForMessage("[Label: label1] is locked by build " + b1.getFullDisplayName(), b3);
56+
isPaused(b3, 1, 1);
57+
58+
// Release b1 — b3 (newest) must acquire before b2
59+
SemaphoreStep.success("wait-inside/1", null);
60+
j.waitForMessage("Lock released on resource", b1);
61+
j.assertBuildStatusSuccess(j.waitForCompletion(b1));
62+
63+
SemaphoreStep.waitForStart("wait-inside/2", b3);
64+
j.waitForMessage("Trying to acquire lock on [Label: label1]", b3);
65+
66+
SemaphoreStep.success("wait-inside/2", null);
67+
j.waitForMessage("Lock released on resource", b3);
68+
j.assertBuildStatusSuccess(j.waitForCompletion(b3));
69+
70+
SemaphoreStep.waitForStart("wait-inside/3", b2);
71+
j.waitForMessage("Trying to acquire lock on [Label: label1]", b2);
72+
73+
SemaphoreStep.success("wait-inside/3", null);
74+
j.assertBuildStatusSuccess(j.waitForCompletion(b2));
75+
}
76+
77+
/**
78+
* Verify that each waiting job's own {@code inversePrecedence} flag controls
79+
* queue ordering, not the releasing job's flag. Uses <b>separate</b> pipeline
80+
* jobs to match the original report.
81+
*
82+
* <pre>
83+
* start time | job | resource | inversePrecedence
84+
* -----------|------|-----------|-------------------
85+
* 00:01 | pA#1 | resource1 | true (acquires)
86+
* 00:02 | pB#1 | resource1 | false (waits — FIFO)
87+
* 00:03 | pA#2 | resource1 | true (waits — inversePrecedence, front)
88+
* 00:04 | pB#2 | resource1 | false (waits — FIFO, behind pB#1)
89+
*
90+
* expected lock order: pA#1 -> pA#2 -> pB#1 -> pB#2
91+
* </pre>
92+
*/
93+
@Test
94+
@Issue({"JENKINS-41070", "GITHUB-864"})
95+
void lockInverseOrderMixedDifferentJobs(JenkinsRule j) throws Exception {
96+
LockableResourcesManager.get().createResourceWithLabel("resource1", "label1");
97+
98+
// Job A — inversePrecedence = true
99+
WorkflowJob pA = j.jenkins.createProject(WorkflowJob.class, "pA");
100+
pA.setDefinition(new CpsFlowDefinition("""
101+
lock(resource: 'resource1', inversePrecedence: true) {
102+
echo 'locked-pA'
103+
semaphore 'wait-inside'
104+
}
105+
echo 'Finish'""", true));
106+
107+
// Job B — inversePrecedence = false
108+
WorkflowJob pB = j.jenkins.createProject(WorkflowJob.class, "pB");
109+
pB.setDefinition(new CpsFlowDefinition("""
110+
lock(resource: 'resource1', inversePrecedence: false) {
111+
echo 'locked-pB'
112+
semaphore 'wait-inside'
113+
}
114+
echo 'Finish'""", true));
115+
116+
// pA#1 acquires the lock
117+
WorkflowRun a1 = pA.scheduleBuild2(0).waitForStart();
118+
SemaphoreStep.waitForStart("wait-inside/1", a1);
119+
j.assertLogContains("locked-pA", a1);
120+
121+
// pB#1 waits (inversePrecedence=false → back of queue)
122+
WorkflowRun b1 = pB.scheduleBuild2(0).waitForStart();
123+
j.waitForMessage("[resource1] is locked by build " + a1.getFullDisplayName(), b1);
124+
125+
// pA#2 waits (inversePrecedence=true → front of queue)
126+
WorkflowRun a2 = pA.scheduleBuild2(0).waitForStart();
127+
j.waitForMessage("[resource1] is locked by build " + a1.getFullDisplayName(), a2);
128+
129+
// pB#2 waits (inversePrecedence=false → back of queue, behind pB#1)
130+
WorkflowRun b2 = pB.scheduleBuild2(0).waitForStart();
131+
j.waitForMessage("[resource1] is locked by build " + a1.getFullDisplayName(), b2);
132+
133+
// Verify only a1 has the lock so far
134+
j.assertLogNotContains("locked-pA", a2);
135+
j.assertLogNotContains("locked-pB", b1);
136+
j.assertLogNotContains("locked-pB", b2);
137+
138+
// Release pA#1 — pA#2 (inversePrecedence=true) must acquire next
139+
SemaphoreStep.success("wait-inside/1", null);
140+
j.waitForMessage("Lock released on resource", a1);
141+
142+
SemaphoreStep.waitForStart("wait-inside/2", a2);
143+
j.assertLogContains("locked-pA", a2);
144+
j.assertLogNotContains("locked-pB", b1);
145+
j.assertLogNotContains("locked-pB", b2);
146+
147+
// Release pA#2 — pB#1 (FIFO among false) must acquire next
148+
SemaphoreStep.success("wait-inside/2", null);
149+
j.waitForMessage("Lock released on resource", a2);
150+
151+
SemaphoreStep.waitForStart("wait-inside/3", b1);
152+
j.assertLogContains("locked-pB", b1);
153+
j.assertLogNotContains("locked-pB", b2);
154+
155+
// Release pB#1 — pB#2 gets the lock last
156+
SemaphoreStep.success("wait-inside/3", null);
157+
j.waitForMessage("Lock released on resource", b1);
158+
159+
SemaphoreStep.waitForStart("wait-inside/4", b2);
160+
j.assertLogContains("locked-pB", b2);
161+
162+
// Release pB#2 and verify all succeed
163+
SemaphoreStep.success("wait-inside/4", null);
164+
165+
j.assertBuildStatusSuccess(j.waitForCompletion(a1));
166+
j.assertBuildStatusSuccess(j.waitForCompletion(a2));
167+
j.assertBuildStatusSuccess(j.waitForCompletion(b1));
168+
j.assertBuildStatusSuccess(j.waitForCompletion(b2));
169+
}
170+
}

0 commit comments

Comments
 (0)