-
Notifications
You must be signed in to change notification settings - Fork 65
Add deterministic archive generation and checksum verification tests #1826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ducminh02
wants to merge
11
commits into
devonfw:main
Choose a base branch
from
ducminh02:1447-cover-checksum-verification-junit
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
2f63724
feat: ensure deterministic archive creation by sorting file streams a…
ducminh02 014725d
test: refactor archive determinism tests to improve readability and u…
ducminh02 dbd9e1c
Merge branch 'main' into 1447-cover-checksum-verification-junit
ducminh02 4f5ffd6
docs: remove entry for issue 1439 from CHANGELOG.adoc
ducminh02 8f019c4
feat: implement SHA-256 checksum generation and verification in MvnRe…
ducminh02 1f24463
test: refactor ChecksumVerificationTest to use JUnit 5 assertion methods
ducminh02 b64e5f0
Merge branch 'main' into 1447-cover-checksum-verification-junit
ducminh02 9a22695
test: add MvnFinalChecksumTest and update MvnRepositoryMock to suppor…
ducminh02 4598e2b
test: refactor MvnFinalChecksumTest to verify installation checksums …
ducminh02 eb1ee61
Merge branch 'main' into 1447-cover-checksum-verification-junit
ducminh02 eb21f87
refactor: use HexUtil for SHA-256 calculation, update determinism tes…
ducminh02 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
cli/src/test/java/com/devonfw/tools/ide/io/ArchiveDeterminismTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| package com.devonfw.tools.ide.io; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.OutputStream; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.attribute.FileTime; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.io.TempDir; | ||
|
|
||
| import com.devonfw.tools.ide.context.AbstractIdeContextTest; | ||
| import com.devonfw.tools.ide.context.IdeTestContext; | ||
|
|
||
| /** | ||
| * Test of archive determinism in {@link FileAccessImpl}. | ||
| */ | ||
| public class ArchiveDeterminismTest extends AbstractIdeContextTest { | ||
|
|
||
| @TempDir | ||
| Path tempDir; | ||
|
|
||
| /** | ||
| * Test that {@link FileAccessImpl#compressTarGz(Path, OutputStream)} is deterministic. | ||
| * | ||
| * @throws IOException if an I/O error occurs. | ||
| */ | ||
| @Test | ||
| public void testTarGzDeterminism() throws IOException, InterruptedException { | ||
|
|
||
| // arrange | ||
| IdeTestContext context = new IdeTestContext(); | ||
| FileAccessImpl fileAccess = new FileAccessImpl(context); | ||
| Path contentDir = this.tempDir.resolve("content"); | ||
| Files.createDirectories(contentDir); | ||
| Files.writeString(contentDir.resolve("file1.txt"), "Content 1"); | ||
| Path binDir = contentDir.resolve("bin"); | ||
| Files.createDirectories(binDir); | ||
| Files.writeString(binDir.resolve("script.sh"), "#!/bin/bash\necho hello"); | ||
|
|
||
| Path archive1 = this.tempDir.resolve("archive1.tar.gz"); | ||
| Path archive2 = this.tempDir.resolve("archive2.tar.gz"); | ||
|
|
||
| // act | ||
| try (OutputStream out1 = Files.newOutputStream(archive1)) { | ||
| fileAccess.compressTarGz(contentDir, out1); | ||
| } | ||
| // Modify modification time of a file to ensure it would affect the hash if not normalized | ||
| Path file1 = contentDir.resolve("file1.txt"); | ||
| FileTime newTime = FileTime.fromMillis(System.currentTimeMillis() + 10000); | ||
| Files.setLastModifiedTime(file1, newTime); | ||
| try (OutputStream out2 = Files.newOutputStream(archive2)) { | ||
| fileAccess.compressTarGz(contentDir, out2); | ||
| } | ||
|
|
||
| // assert | ||
| assertThat(archive1).hasSameBinaryContentAs(archive2); | ||
| } | ||
|
|
||
| /** | ||
| * Test that {@link FileAccessImpl#compressZip(Path, OutputStream)} is deterministic. | ||
| * | ||
| * @throws IOException if an I/O error occurs. | ||
| */ | ||
| @Test | ||
| public void testZipDeterminism() throws IOException, InterruptedException { | ||
|
|
||
| // arrange | ||
| IdeTestContext context = new IdeTestContext(); | ||
| FileAccessImpl fileAccess = new FileAccessImpl(context); | ||
| Path contentDir = this.tempDir.resolve("content-zip"); | ||
| Files.createDirectories(contentDir); | ||
| Files.writeString(contentDir.resolve("file1.txt"), "Content 1"); | ||
|
|
||
| Path archive1 = this.tempDir.resolve("archive1.zip"); | ||
| Path archive2 = this.tempDir.resolve("archive2.zip"); | ||
|
|
||
| // act | ||
| try (OutputStream out1 = Files.newOutputStream(archive1)) { | ||
| fileAccess.compressZip(contentDir, out1); | ||
| } | ||
| // Modify modification time of a file to ensure it would affect the hash if not normalized | ||
| Path file1 = contentDir.resolve("file1.txt"); | ||
| FileTime newTime = FileTime.fromMillis(System.currentTimeMillis() + 10000); | ||
| Files.setLastModifiedTime(file1, newTime); | ||
| try (OutputStream out2 = Files.newOutputStream(archive2)) { | ||
| fileAccess.compressZip(contentDir, out2); | ||
| } | ||
|
|
||
| // assert | ||
| assertThat(archive1).hasSameBinaryContentAs(archive2); | ||
| } | ||
| } |
49 changes: 49 additions & 0 deletions
49
cli/src/test/java/com/devonfw/tools/ide/tool/mvn/MvnFinalChecksumTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.devonfw.tools.ide.tool.mvn; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import com.devonfw.tools.ide.context.AbstractIdeContextTest; | ||
| import com.devonfw.tools.ide.context.IdeTestContext; | ||
| import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; | ||
| import com.github.tomakehurst.wiremock.junit5.WireMockTest; | ||
|
|
||
| /** | ||
| * Integration test verifying that the deterministic archives produced by our mock repositories | ||
| * match our hardcoded "Gold Standard" values. | ||
| */ | ||
| @WireMockTest | ||
| class MvnFinalChecksumTest extends AbstractIdeContextTest { | ||
|
|
||
| private static final String PROJECT_MVN = "mvn"; | ||
|
|
||
|
|
||
|
|
||
| /** | ||
| * Integration test verifying that the tool installation correctly performs and validates | ||
| * the checksum from the URL repository (urls.sha256). | ||
| */ | ||
| @Test | ||
| void testVerifyUrlChecksum(WireMockRuntimeInfo wmRuntimeInfo) throws IOException { | ||
|
|
||
| // 1. Arrange: Use the "mvn" project context with WireMock | ||
| IdeTestContext context = newContext(PROJECT_MVN, wmRuntimeInfo); | ||
| Mvn mvn = context.getCommandletManager().getCommandlet(Mvn.class); | ||
|
|
||
| // Read the expected hash from the URL repository file in the test resources | ||
| Path urlsSha256Path = context.getIdePath().resolve("urls/mvn/mvn/3.9.7/urls.sha256"); | ||
| String expectedSha256 = Files.readString(urlsSha256Path).trim(); | ||
|
|
||
| // 2. Act: Trigger the installation of Maven 3.9.7 | ||
| // This will use ToolRepositoryMock which triggers deterministic compression | ||
| // and verifies it against urls.sha256 in the test resources. | ||
| mvn.install(); | ||
|
|
||
| // 3. Assert: Verify the installation and checksum success logs | ||
| assertThat(context).logAtSuccess().hasMessageContaining("Successfully installed mvn in version 3.9.7"); | ||
| assertThat(context).logAtSuccess().hasMessageContaining("SHA-256 checksum " + expectedSha256 + " is correct."); | ||
| } | ||
| } |
104 changes: 104 additions & 0 deletions
104
cli/src/test/java/com/devonfw/tools/ide/tool/repository/ChecksumVerificationTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package com.devonfw.tools.ide.tool.repository; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.io.TempDir; | ||
|
|
||
| import com.devonfw.tools.ide.cli.CliException; | ||
| import com.devonfw.tools.ide.context.AbstractIdeContextTest; | ||
| import com.devonfw.tools.ide.context.IdeTestContext; | ||
| import com.devonfw.tools.ide.url.model.file.UrlGenericChecksum; | ||
|
|
||
| /** | ||
| * Test of checksum verification in {@link AbstractToolRepository}. | ||
| */ | ||
| public class ChecksumVerificationTest extends AbstractIdeContextTest { | ||
|
|
||
| @TempDir | ||
| Path tempDir; | ||
|
|
||
| /** | ||
| * Test {@link AbstractToolRepository#verifyChecksum(Path, UrlGenericChecksum)} with matching checksum. | ||
| * | ||
| * @throws IOException if an I/O error occurs. | ||
| */ | ||
| @Test | ||
| public void testVerifyChecksumMatching() throws IOException { | ||
|
|
||
| // arrange | ||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
| AbstractToolRepository repo = new DefaultToolRepository(context); | ||
| Path file = this.tempDir.resolve("testfile.txt"); | ||
| String content = "Hello World"; | ||
| Files.writeString(file, content); | ||
| String checksum = context.getFileAccess().checksum(file, "SHA-256"); | ||
| UrlGenericChecksum expectedChecksum = new TestUrlGenericChecksum(checksum, "SHA-256"); | ||
|
|
||
| // act & assert | ||
| assertDoesNotThrow(() -> { | ||
| repo.verifyChecksum(file, expectedChecksum); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Test {@link AbstractToolRepository#verifyChecksum(Path, UrlGenericChecksum)} with mismatching checksum. | ||
| * | ||
| * @throws IOException if an I/O error occurs. | ||
| */ | ||
| @Test | ||
| public void testVerifyChecksumMismatch() throws IOException { | ||
|
|
||
| // arrange | ||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
| AbstractToolRepository repo = new DefaultToolRepository(context); | ||
| Path file = this.tempDir.resolve("testfile.txt"); | ||
| String content = "Hello World"; | ||
| Files.writeString(file, content); | ||
| String wrongChecksum = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // SHA-256 of empty string | ||
| UrlGenericChecksum expectedChecksum = new TestUrlGenericChecksum(wrongChecksum, "SHA-256"); | ||
|
|
||
| // act & assert | ||
| CliException e = assertThrows(CliException.class, () -> { | ||
| repo.verifyChecksum(file, expectedChecksum); | ||
| }); | ||
| assertThat(e).hasMessageContaining("has the wrong SHA-256 checksum"); | ||
| assertThat(e).hasMessageContaining("Expected " + wrongChecksum); | ||
| } | ||
|
|
||
| private static class TestUrlGenericChecksum implements UrlGenericChecksum { | ||
|
|
||
| private final String checksum; | ||
|
|
||
| private final String algorithm; | ||
|
|
||
| public TestUrlGenericChecksum(String checksum, String algorithm) { | ||
|
|
||
| this.checksum = checksum; | ||
| this.algorithm = algorithm; | ||
| } | ||
|
|
||
| @Override | ||
| public String getChecksum() { | ||
|
|
||
| return this.checksum; | ||
| } | ||
|
|
||
| @Override | ||
| public String getHashAlgorithm() { | ||
|
|
||
| return this.algorithm; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
|
|
||
| return this.checksum; | ||
| } | ||
| } | ||
| } |
1 change: 1 addition & 0 deletions
1
cli/src/test/resources/ide-projects/mvn/_ide/urls/mvn/mvn/3.9.7/urls.sha256
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| e3aac9e671f88f0e14dcf8240a5301778fd335e14b8752d15fa2f28b1a51ab32 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.