|
18 | 18 | import java.util.HashSet; |
19 | 19 | import java.util.Iterator; |
20 | 20 | import java.util.List; |
| 21 | +import java.util.Map; |
21 | 22 | import java.util.Set; |
| 23 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 24 | +import java.util.concurrent.atomic.AtomicReference; |
22 | 25 | import java.util.jar.Manifest; |
23 | 26 | import java.util.regex.Pattern; |
24 | 27 |
|
|
39 | 42 | import aQute.bnd.osgi.Processor; |
40 | 43 | import aQute.bnd.osgi.Resource; |
41 | 44 | import aQute.bnd.osgi.eclipse.EclipseClasspath; |
| 45 | +import aQute.bnd.service.JarLifecycleListener; |
42 | 46 | import aQute.bnd.service.RepositoryPlugin; |
43 | 47 | import aQute.bnd.service.Strategy; |
44 | 48 | import aQute.bnd.test.jupiter.InjectTemporaryDirectory; |
@@ -547,181 +551,73 @@ private void stale(Project project, boolean b) throws Exception { |
547 | 551 | } |
548 | 552 |
|
549 | 553 | /** |
550 | | - * Check that the content-hash optimization prevents unnecessary JAR |
551 | | - * rewrites when the JAR content is unchanged between builds. This avoids |
552 | | - * cascading rebuilds of dependent projects. |
| 554 | + * Verify that JarArtifactLifecycleListener plugins are called at the right |
| 555 | + * times. |
553 | 556 | */ |
554 | 557 | @Test |
555 | | - public void testContentHashSkipsBuildWhenUnchanged() throws Exception { |
| 558 | + public void testJarArtifactLifecycleListenerIntegration() throws Exception { |
556 | 559 | Workspace ws = getWorkspace(IO.getFile("testresources/ws")); |
557 | | - ws.setRebuildTriggerPolicy("api"); |
558 | 560 | Project project = ws.getProject("p-stale"); |
559 | 561 | assertNotNull(project); |
560 | 562 |
|
561 | | - // First build - should create JAR and digest files |
562 | | - File[] firstBuild = project.build(); |
563 | | - assertNotNull(firstBuild); |
564 | | - assertTrue(firstBuild.length > 0); |
565 | | - |
566 | | - File jarFile = firstBuild[0]; |
567 | | - assertTrue(jarFile.isFile()); |
568 | | - long firstTimestamp = jarFile.lastModified(); |
569 | | - |
570 | | - // Verify digest file was created |
571 | | - File digestFile = getDigestFile(jarFile); |
572 | | - assertTrue(digestFile.isFile(), "Digest file should be created after build"); |
573 | | - String firstDigest = IO.collect(digestFile) |
574 | | - .trim(); |
575 | | - assertFalse(firstDigest.isEmpty(), "Digest should not be empty"); |
576 | | - |
577 | | - // Simulate time passing by adjusting the JAR timestamp backward, |
578 | | - // then mark the project as changed. If the optimization works |
579 | | - // correctly, the JAR's timestamp will be restored to this value |
580 | | - // since the content hasn't changed. |
581 | | - long adjustedTimestamp = firstTimestamp - 10000; |
582 | | - jarFile.setLastModified(adjustedTimestamp); |
583 | | - |
584 | | - // Mark project as changed so it rebuilds |
585 | | - project.setChanged(); |
586 | | - project.refresh(); |
587 | | - |
588 | | - // Second build - content is unchanged, JAR timestamp should be |
589 | | - // preserved |
590 | | - File[] secondBuild = project.build(); |
591 | | - assertNotNull(secondBuild); |
592 | | - assertTrue(secondBuild.length > 0); |
593 | | - |
594 | | - File jarFile2 = secondBuild[0]; |
595 | | - assertTrue(jarFile2.isFile()); |
596 | | - |
597 | | - // The JAR timestamp should be preserved since content didn't change |
598 | | - assertEquals(adjustedTimestamp, jarFile2.lastModified(), |
599 | | - "JAR timestamp should be preserved when content is unchanged"); |
600 | | - |
601 | | - // Digest file should still exist with the same content |
602 | | - assertTrue(digestFile.isFile()); |
603 | | - String secondDigest = IO.collect(digestFile) |
604 | | - .trim(); |
605 | | - assertEquals(firstDigest, secondDigest, "Digest should be unchanged"); |
606 | | - } |
| 563 | + // Register a mock listener to verify it's called |
| 564 | + AtomicBoolean beforeCalled = new AtomicBoolean(); |
| 565 | + AtomicBoolean afterCalled = new AtomicBoolean(); |
607 | 566 |
|
608 | | - /** |
609 | | - * Check that when JAR content actually changes, the JAR is rewritten and |
610 | | - * the digest is updated. |
611 | | - */ |
612 | | - @Test |
613 | | - public void testContentHashRewritesWhenChanged() throws Exception { |
614 | | - Workspace ws = getWorkspace(IO.getFile("testresources/ws")); |
615 | | - ws.setRebuildTriggerPolicy("api"); |
616 | | - Project project = ws.getProject("p-stale"); |
617 | | - assertNotNull(project); |
| 567 | + JarLifecycleListener mockListener = new JarLifecycleListener() { |
| 568 | + @Override |
| 569 | + public void beforeWrite(Project p, Jar jar, File outputFile, Map<String, Object> context) { |
| 570 | + beforeCalled.set(true); |
| 571 | + } |
618 | 572 |
|
619 | | - // First build |
620 | | - File[] firstBuild = project.build(); |
621 | | - assertNotNull(firstBuild); |
622 | | - File jarFile = firstBuild[0]; |
623 | | - File digestFile = getDigestFile(jarFile); |
624 | | - assertTrue(digestFile.isFile()); |
625 | | - String firstDigest = IO.collect(digestFile) |
626 | | - .trim(); |
627 | | - |
628 | | - // Change the project content so the JAR will be different |
629 | | - project.setChanged(); |
630 | | - project.refresh(); |
631 | | - project.setProperty("Include-Resource", "p;literal=\"changed content\""); |
632 | | - |
633 | | - // Second build - content changed, JAR should be rewritten |
634 | | - File[] secondBuild = project.build(); |
635 | | - assertNotNull(secondBuild); |
636 | | - File jarFile2 = secondBuild[0]; |
637 | | - |
638 | | - // Digest should have changed |
639 | | - assertTrue(digestFile.isFile()); |
640 | | - String secondDigest = IO.collect(digestFile) |
641 | | - .trim(); |
642 | | - assertThat(secondDigest).as("Digest should change when content changes") |
643 | | - .isNotEqualTo(firstDigest); |
644 | | - } |
| 573 | + @Override |
| 574 | + public void afterWrite(Project p, File outputFile, Jar jar, Map<String, Object> context) { |
| 575 | + afterCalled.set(true); |
| 576 | + } |
| 577 | + }; |
645 | 578 |
|
646 | | - private static File getDigestFile(File jarFile) { |
647 | | - return new File(jarFile.getParentFile(), jarFile.getName() + ".digest"); |
| 579 | + ws.addBasicPlugin(mockListener); |
| 580 | + |
| 581 | + // Build |
| 582 | + File[] built = project.build(); |
| 583 | + assertNotNull(built); |
| 584 | + assertThat(built.length).isGreaterThan(0); |
| 585 | + |
| 586 | + // Verify listeners were called |
| 587 | + assertThat(beforeCalled).isTrue(); |
| 588 | + assertThat(afterCalled).isTrue(); |
648 | 589 | } |
649 | 590 |
|
650 | 591 | /** |
651 | | - * Check that the JAR timestamp is preserved when the content changes |
652 | | - * Verify that the JAR's timestamp is preserved when the JAR content |
653 | | - * changes but the exported API surface remains identical. The |
654 | | - * optimization works by comparing a stored API-digest sidecar file |
655 | | - * with the newly computed API digest during build. When they match, |
656 | | - * the old file timestamp is kept so that downstream timestamp-based |
657 | | - * staleness checks don't trigger unnecessary cascade rebuilds. |
658 | | - * <p> |
659 | | - * Since the p-stale test project is {@code -resourceonly} (no |
660 | | - * exported packages), this test simulates the mechanism directly: |
661 | | - * we write a known API digest, change the project content so the |
662 | | - * full content digest changes, and verify that timestamp |
663 | | - * preservation still engages via the API digest fallback. |
| 592 | + * Verify that WriteContext is passed correctly and allows data exchange. |
664 | 593 | */ |
665 | 594 | @Test |
666 | | - public void testApiDigestPreservesTimestampWhenApiUnchanged() throws Exception { |
| 595 | + public void testJarArtifactWriteContextDataExchange() throws Exception { |
667 | 596 | Workspace ws = getWorkspace(IO.getFile("testresources/ws")); |
668 | | - ws.setRebuildTriggerPolicy("api"); |
669 | 597 | Project project = ws.getProject("p-stale"); |
670 | | - assertNotNull(project); |
671 | 598 |
|
672 | | - // First build — establishes the baseline JAR and digest files |
673 | | - File[] firstBuild = project.build(); |
674 | | - assertNotNull(firstBuild); |
675 | | - assertTrue(firstBuild.length > 0); |
676 | | - |
677 | | - File jarFile = firstBuild[0]; |
678 | | - assertTrue(jarFile.isFile()); |
679 | | - |
680 | | - // Record the old timestamp and adjust it to simulate time |
681 | | - long adjustedTimestamp = jarFile.lastModified() - 10000; |
682 | | - jarFile.setLastModified(adjustedTimestamp); |
683 | | - |
684 | | - // The content digest from the first build |
685 | | - File digestFile = getDigestFile(jarFile); |
686 | | - assertTrue(digestFile.isFile(), "Content digest file should exist after build"); |
687 | | - |
688 | | - // Write a fake API digest sidecar. The next build will produce |
689 | | - // the same resource-only bundle (no Export-Package), so |
690 | | - // calcApiDigest() returns null. However, when we change the |
691 | | - // project content the content digest will differ. To simulate |
692 | | - // the API-digest-unchanged path, we need a non-null API digest. |
693 | | - // We'll change the content, then verify the mechanism by |
694 | | - // checking the timestamp. Since calcApiDigest returns null for |
695 | | - // resource-only bundles, the API digest path won't engage here, |
696 | | - // but we can verify the infrastructure is correctly wired: |
697 | | - // the API digest file should remain from the first build if no |
698 | | - // new one is computed. |
699 | | - File apiDigestFile = new File(jarFile.getParentFile(), jarFile.getName() + ".api-digest"); |
700 | | - |
701 | | - // Change the project content so the JAR will differ |
702 | | - project.setChanged(); |
703 | | - project.refresh(); |
704 | | - project.setProperty("Include-Resource", "p;literal=\"changed content\""); |
705 | | - |
706 | | - // Rebuild — content changed so content digest differs, and |
707 | | - // calcApiDigest returns null for resource-only bundles. The |
708 | | - // timestamp should NOT be preserved (no API digest fallback). |
709 | | - File[] secondBuild = project.build(); |
710 | | - assertNotNull(secondBuild); |
711 | | - |
712 | | - File jarFile2 = secondBuild[0]; |
713 | | - // Content changed so the digest should differ |
714 | | - String newDigest = IO.collect(digestFile).trim(); |
715 | | - // The JAR should have a new timestamp since both content and |
716 | | - // API digests indicate a change (or API digest is absent) |
717 | | - assertThat(jarFile2.lastModified()) |
718 | | - .as("Timestamp should NOT be preserved when content changes and no API digest exists") |
719 | | - .isNotEqualTo(adjustedTimestamp); |
720 | | - |
721 | | - // The content digest file should still exist |
722 | | - assertTrue(digestFile.isFile(), "Content digest file should exist after rebuild"); |
| 599 | + AtomicReference<String> beforeData = new AtomicReference<>(); |
| 600 | + AtomicReference<String> afterData = new AtomicReference<>(); |
| 601 | + |
| 602 | + JarLifecycleListener testListener = new JarLifecycleListener() { |
| 603 | + @Override |
| 604 | + public void beforeWrite(Project p, Jar jar, File outputFile, Map<String, Object> context) { |
| 605 | + context.put("testKey", "testValue"); |
| 606 | + } |
| 607 | + |
| 608 | + @Override |
| 609 | + public void afterWrite(Project p, File outputFile, Jar jar, Map<String, Object> context) { |
| 610 | + afterData.set((String) context.get("testKey")); |
| 611 | + } |
| 612 | + }; |
| 613 | + |
| 614 | + ws.addBasicPlugin(testListener); |
| 615 | + project.build(); |
| 616 | + |
| 617 | + assertThat(afterData.get()).isEqualTo("testValue"); |
723 | 618 | } |
724 | 619 |
|
| 620 | + |
725 | 621 | /** |
726 | 622 | * Check multiple repos |
727 | 623 | * |
|
0 commit comments