Skip to content

Commit e736ca9

Browse files
committed
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)
1 parent b069f1f commit e736ca9

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,155 @@ void lockInverseOrder2(JenkinsRule j) throws Exception {
557557
j.assertBuildStatusSuccess(j.waitForCompletion(b6));
558558
}
559559

560+
/**
561+
* Verify that inversePrecedence=true grants the lock to the newest build
562+
* when locking by <b>label</b> (not named resource).
563+
*
564+
* <pre>
565+
* start time | build | label | inversePrecedence
566+
* -----------|-------|--------|-------------------
567+
* 00:01 | b1 | label1 | true (acquires)
568+
* 00:02 | b2 | label1 | true (waits)
569+
* 00:03 | b3 | label1 | true (waits)
570+
*
571+
* expected lock order: b1 -> b3 -> b2
572+
* </pre>
573+
*/
574+
@Test
575+
@Issue({"JENKINS-40787", "GITHUB-861"})
576+
void lockInverseOrderWithLabel(JenkinsRule j) throws Exception {
577+
LockableResourcesManager.get().createResourceWithLabel("resource1", "label1");
578+
WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p");
579+
p.setDefinition(new CpsFlowDefinition("""
580+
lock(label: 'label1', inversePrecedence: true) {
581+
semaphore 'wait-inside'
582+
}
583+
echo 'Finish'""", true));
584+
585+
WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
586+
SemaphoreStep.waitForStart("wait-inside/1", b1);
587+
588+
WorkflowRun b2 = p.scheduleBuild2(0).waitForStart();
589+
j.waitForMessage("[Label: label1] is locked by build " + b1.getFullDisplayName(), b2);
590+
isPaused(b2, 1, 1);
591+
592+
WorkflowRun b3 = p.scheduleBuild2(0).waitForStart();
593+
j.waitForMessage("[Label: label1] is locked by build " + b1.getFullDisplayName(), b3);
594+
isPaused(b3, 1, 1);
595+
596+
// Release b1 — b3 (newest) must acquire before b2
597+
SemaphoreStep.success("wait-inside/1", null);
598+
j.waitForMessage("Lock released on resource", b1);
599+
j.assertBuildStatusSuccess(j.waitForCompletion(b1));
600+
601+
SemaphoreStep.waitForStart("wait-inside/2", b3);
602+
j.waitForMessage("Trying to acquire lock on [Label: label1]", b3);
603+
604+
SemaphoreStep.success("wait-inside/2", null);
605+
j.waitForMessage("Lock released on resource", b3);
606+
j.assertBuildStatusSuccess(j.waitForCompletion(b3));
607+
608+
SemaphoreStep.waitForStart("wait-inside/3", b2);
609+
j.waitForMessage("Trying to acquire lock on [Label: label1]", b2);
610+
611+
SemaphoreStep.success("wait-inside/3", null);
612+
j.assertBuildStatusSuccess(j.waitForCompletion(b2));
613+
}
614+
615+
/**
616+
* Verify that each waiting job's own {@code inversePrecedence} flag controls
617+
* queue ordering, not the releasing job's flag. Uses <b>separate</b> pipeline
618+
* jobs to match the original report.
619+
*
620+
* <pre>
621+
* start time | job | resource | inversePrecedence
622+
* -----------|------|-----------|-------------------
623+
* 00:01 | pA#1 | resource1 | true (acquires)
624+
* 00:02 | pB#1 | resource1 | false (waits — FIFO)
625+
* 00:03 | pA#2 | resource1 | true (waits — inversePrecedence, front)
626+
* 00:04 | pB#2 | resource1 | false (waits — FIFO, behind pB#1)
627+
*
628+
* expected lock order: pA#1 -> pA#2 -> pB#1 -> pB#2
629+
* </pre>
630+
*/
631+
@Test
632+
@Issue({"JENKINS-41070", "GITHUB-864"})
633+
void lockInverseOrderMixedDifferentJobs(JenkinsRule j) throws Exception {
634+
LockableResourcesManager.get().createResourceWithLabel("resource1", "label1");
635+
636+
// Job A — inversePrecedence = true
637+
WorkflowJob pA = j.jenkins.createProject(WorkflowJob.class, "pA");
638+
pA.setDefinition(new CpsFlowDefinition("""
639+
lock(resource: 'resource1', inversePrecedence: true) {
640+
echo 'locked-pA'
641+
semaphore 'wait-inside'
642+
}
643+
echo 'Finish'""", true));
644+
645+
// Job B — inversePrecedence = false
646+
WorkflowJob pB = j.jenkins.createProject(WorkflowJob.class, "pB");
647+
pB.setDefinition(new CpsFlowDefinition("""
648+
lock(resource: 'resource1', inversePrecedence: false) {
649+
echo 'locked-pB'
650+
semaphore 'wait-inside'
651+
}
652+
echo 'Finish'""", true));
653+
654+
// pA#1 acquires the lock
655+
WorkflowRun a1 = pA.scheduleBuild2(0).waitForStart();
656+
SemaphoreStep.waitForStart("wait-inside/1", a1);
657+
j.assertLogContains("locked-pA", a1);
658+
659+
// pB#1 waits (inversePrecedence=false → back of queue)
660+
WorkflowRun b1 = pB.scheduleBuild2(0).waitForStart();
661+
j.waitForMessage("[resource1] is locked by build " + a1.getFullDisplayName(), b1);
662+
663+
// pA#2 waits (inversePrecedence=true → front of queue)
664+
WorkflowRun a2 = pA.scheduleBuild2(0).waitForStart();
665+
j.waitForMessage("[resource1] is locked by build " + a1.getFullDisplayName(), a2);
666+
667+
// pB#2 waits (inversePrecedence=false → back of queue, behind pB#1)
668+
WorkflowRun b2 = pB.scheduleBuild2(0).waitForStart();
669+
j.waitForMessage("[resource1] is locked by build " + a1.getFullDisplayName(), b2);
670+
671+
// Verify only a1 has the lock so far
672+
j.assertLogNotContains("locked-pA", a2);
673+
j.assertLogNotContains("locked-pB", b1);
674+
j.assertLogNotContains("locked-pB", b2);
675+
676+
// Release pA#1 — pA#2 (inversePrecedence=true) must acquire next
677+
SemaphoreStep.success("wait-inside/1", null);
678+
j.waitForMessage("Lock released on resource", a1);
679+
680+
SemaphoreStep.waitForStart("wait-inside/2", a2);
681+
j.assertLogContains("locked-pA", a2);
682+
j.assertLogNotContains("locked-pB", b1);
683+
j.assertLogNotContains("locked-pB", b2);
684+
685+
// Release pA#2 — pB#1 (FIFO among false) must acquire next
686+
SemaphoreStep.success("wait-inside/2", null);
687+
j.waitForMessage("Lock released on resource", a2);
688+
689+
SemaphoreStep.waitForStart("wait-inside/3", b1);
690+
j.assertLogContains("locked-pB", b1);
691+
j.assertLogNotContains("locked-pB", b2);
692+
693+
// Release pB#1 — pB#2 gets the lock last
694+
SemaphoreStep.success("wait-inside/3", null);
695+
j.waitForMessage("Lock released on resource", b1);
696+
697+
SemaphoreStep.waitForStart("wait-inside/4", b2);
698+
j.assertLogContains("locked-pB", b2);
699+
700+
// Release pB#2 and verify all succeed
701+
SemaphoreStep.success("wait-inside/4", null);
702+
703+
j.assertBuildStatusSuccess(j.waitForCompletion(a1));
704+
j.assertBuildStatusSuccess(j.waitForCompletion(a2));
705+
j.assertBuildStatusSuccess(j.waitForCompletion(b1));
706+
j.assertBuildStatusSuccess(j.waitForCompletion(b2));
707+
}
708+
560709
/**
561710
* start time | job | resource | priority
562711
* ------ |--- |--- |---

0 commit comments

Comments
 (0)