diff --git a/.github/actions/align-serializer-version/action.yml b/.github/actions/align-serializer-version/action.yml new file mode 100644 index 000000000..69abea3c1 --- /dev/null +++ b/.github/actions/align-serializer-version/action.yml @@ -0,0 +1,45 @@ +name: Align serializer version +description: >- + Computes the branch-suffixed snapshot version for the current branch and, when a + branch of the same name exists in eclipse-serializer/serializer, rewrites the + eclipse.serializer.version POM property to that serializer branch snapshot. This is + the cross-repo coupling used so a store branch builds against its matching serializer + branch. No-op on main / release branches. Requires Maven/JDK to be set up beforehand. + +inputs: + github-token: + description: Token used to query branches of the serializer repository. + required: true + +outputs: + version: + description: The branch-suffixed snapshot version (e.g. 5.0.0-foo_bar-1a2b3c4d5e-SNAPSHOT). + value: ${{ steps.compute.outputs.version }} + aligned: + description: "'true' if eclipse.serializer.version was rewritten to the serializer branch snapshot." + value: ${{ steps.compute.outputs.aligned }} + +runs: + using: composite + steps: + - id: compute + shell: bash + run: | + BRANCH=${GITHUB_REF#refs/heads/} + currentVersion=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) + suffix=$(echo -n "$BRANCH" | tr '/' '_' | cut -c1-10)-$(echo -n "$BRANCH" | md5sum | cut -c1-10) + newVersion="${currentVersion%-SNAPSHOT}-${suffix}-SNAPSHOT" + echo "version=$newVersion" >> "$GITHUB_OUTPUT" + + aligned=false + if [ "$BRANCH" != "main" ] && [[ "$BRANCH" != release/* ]]; then + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ inputs.github-token }}" https://api.github.com/repos/eclipse-serializer/serializer/branches/$BRANCH) + if [ "$RESPONSE" -eq 200 ]; then + echo "Matching serializer branch found; aligning eclipse.serializer.version to $newVersion" + mvn versions:set-property -Dproperty=eclipse.serializer.version -DnewVersion=$newVersion --batch-mode + aligned=true + else + echo "No matching serializer branch; keeping default eclipse.serializer.version" + fi + fi + echo "aligned=$aligned" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/maven_build.yml b/.github/workflows/maven_build.yml index 02aeb7456..b89e085df 100644 --- a/.github/workflows/maven_build.yml +++ b/.github/workflows/maven_build.yml @@ -22,6 +22,10 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'maven' + - name: Align serializer version to matching branch snapshot + uses: ./.github/actions/align-serializer-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build with Maven run: mvn -T 1C -P examples -B clean package --file pom.xml -U diff --git a/.github/workflows/maven_build_win.yml b/.github/workflows/maven_build_win.yml index eba7b1818..5297af086 100644 --- a/.github/workflows/maven_build_win.yml +++ b/.github/workflows/maven_build_win.yml @@ -22,5 +22,9 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'maven' + - name: Align serializer version to matching branch snapshot + uses: ./.github/actions/align-serializer-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build with Maven run: mvn -T 1C -P examples -B clean package --file pom.xml -U diff --git a/.github/workflows/maven_converter.yml b/.github/workflows/maven_converter.yml index 94d26244e..da311cb7b 100644 --- a/.github/workflows/maven_converter.yml +++ b/.github/workflows/maven_converter.yml @@ -21,5 +21,9 @@ jobs: with: java-version: '17' distribution: 'temurin' + - name: Align serializer version to matching branch snapshot + uses: ./.github/actions/align-serializer-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build with Maven run: mvn -B -Pconverter-standalone -pl storage/embedded-tools/storage-converter -am clean package --file pom.xml -U diff --git a/.github/workflows/maven_deploy_snapshot_dev.yml b/.github/workflows/maven_deploy_snapshot_dev.yml index d38fd9262..cce123677 100644 --- a/.github/workflows/maven_deploy_snapshot_dev.yml +++ b/.github/workflows/maven_deploy_snapshot_dev.yml @@ -24,26 +24,13 @@ jobs: server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - - name: Prepare suffix - run: | - suffix=$(echo -n "${GITHUB_REF#refs/heads/}" | tr '/' '_' | cut -c1-10)-$(echo -n "${GITHUB_REF#refs/heads/}" | md5sum | cut -c1-10) - echo "Suffix: $suffix" - echo "SUFFIX=$suffix" >> $GITHUB_ENV + - name: Align serializer version + id: align + uses: ./.github/actions/align-serializer-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Update project version - run: | - currentVersion=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) - currentVersionWithoutSnapshot=${currentVersion%-SNAPSHOT} - newVersion="${currentVersionWithoutSnapshot}-$SUFFIX-SNAPSHOT" - mvn versions:set -DnewVersion=$newVersion --batch-mode - BRANCH_NAME=${{ github.ref }} - REPO_OWNER="eclipse-serializer" - REPO_NAME="serializer" - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/branches/$BRANCH_NAME) - if [ $RESPONSE -eq 200 ]; then - mvn versions:set-property -Dproperty=eclipse.serializer.version -DnewVersion=$newVersion - else - echo "Branch does not exist in serializer repository, skipping serializer version change" - fi + run: mvn versions:set -DnewVersion=${{ steps.align.outputs.version }} --batch-mode - name: Make a snapshot run: mvn -T 1C -Pdeploy -Pproduction --no-transfer-progress --batch-mode clean deploy -U -Dmaven.javadoc.skip=true -Dgpg.skip=true -Dlicense.skip=true -Denforcer.skip=true env: diff --git a/.github/workflows/maven_it_test.yml b/.github/workflows/maven_it_test.yml new file mode 100644 index 000000000..77312784a --- /dev/null +++ b/.github/workflows/maven_it_test.yml @@ -0,0 +1,45 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Integration tests (vmt) + +permissions: + contents: read + checks: write + +on: + push: + branches: + - '**' + pull_request: + branches: [ main ] + +jobs: + integration-tests: + runs-on: ubuntu-latest + steps: + #Build with java 17 + - uses: actions/checkout@v6 + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' + - name: Align serializer version to matching branch snapshot + uses: ./.github/actions/align-serializer-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build reactor dependencies (no tests) + run: mvn -T 1C -P IT -B clean install -DskipTests -pl integration-tests -am --no-transfer-progress --file pom.xml -U -Dmaven.javadoc.skip=true -Dgpg.skip=true -Dlicense.skip=true -Denforcer.skip=true + - name: Run integration tests + run: mvn -P IT -B test -pl integration-tests --no-transfer-progress --file pom.xml -Dgpg.skip=true -Dlicense.skip=true -Denforcer.skip=true + + - name: Report JUnit results (dorny - surefire) + if: always() + uses: dorny/test-reporter@v2 + with: + name: "Integration tests (vmt) Results" + path: 'integration-tests/target/surefire-reports/*.xml' + reporter: java-junit + fail-on-error: false diff --git a/.github/workflows/maven_slow_test.yml b/.github/workflows/maven_slow_test.yml index d933e15f5..b21defde9 100644 --- a/.github/workflows/maven_slow_test.yml +++ b/.github/workflows/maven_slow_test.yml @@ -26,6 +26,10 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'maven' + - name: Align serializer version to matching branch snapshot + uses: ./.github/actions/align-serializer-version + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Run slow tests run: mvn -T 1C -P examples -P slow-tests -B clean test --no-transfer-progress --file pom.xml -U -Dmaven.javadoc.skip=true -Dgpg.skip=true -Dlicense.skip=true -Denforcer.skip=true diff --git a/integration-tests/LICENSE b/integration-tests/LICENSE new file mode 100644 index 000000000..d3087e4c5 --- /dev/null +++ b/integration-tests/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml new file mode 100644 index 000000000..5f2016806 --- /dev/null +++ b/integration-tests/pom.xml @@ -0,0 +1,182 @@ + + + 4.0.0 + + + org.eclipse.store + store-parent + 5.0.0-SNAPSHOT + ../pom.xml + + + integration-tests + EclipseStore Integration Tests + https://projects.eclipse.org/projects/technology.store + + + true + 6.0.3 + + + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + + + + + + + + org.eclipse.store + storage-embedded + ${project.version} + + + org.eclipse.store + storage-embedded-configuration + ${project.version} + + + org.eclipse.store + storage-embedded-tools-storage-converter + ${project.version} + + + org.eclipse.store + gigamap + ${project.version} + + + org.eclipse.store + cache + ${project.version} + + + + + org.eclipse.serializer + persistence-binary-jdk8 + ${eclipse.serializer.version} + + + org.eclipse.serializer + configuration-yaml + ${eclipse.serializer.version} + + + org.eclipse.serializer + configuration-hocon + ${eclipse.serializer.version} + + + org.eclipse.serializer + nativememory + ${eclipse.serializer.version} + + + org.eclipse.serializer + serializer + ${eclipse.serializer.version} + test + + + org.eclipse.serializer + test-fixtures + ${eclipse.serializer.version} + test + + + + + org.junit.jupiter + junit-jupiter + test + + + org.awaitility + awaitility + 4.2.2 + test + + + net.datafaker + datafaker + 2.5.3 + test + + + org.apache.commons + commons-lang3 + 3.20.0 + test + + + commons-io + commons-io + 2.21.0 + test + + + org.javassist + javassist + 3.30.2-GA + test + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + false + + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.lang.ref=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.base/java.net=ALL-UNNAMED + --add-opens java.base/java.time=ALL-UNNAMED + --add-opens java.base/java.time.zone=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED + --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED + --add-opens java.base/java.util.regex=ALL-UNNAMED + --add-opens java.base/sun.net.www.protocol.http=ALL-UNNAMED + --add-opens java.base/sun.nio.fs=ALL-UNNAMED + --add-opens java.base/sun.util.calendar=ALL-UNNAMED + --add-exports java.base/jdk.internal.misc=ALL-UNNAMED + + + + + + diff --git a/integration-tests/src/test/java/test/eclipse/store/behavior/BackupTxLogDuplicateBugRepro.java b/integration-tests/src/test/java/test/eclipse/store/behavior/BackupTxLogDuplicateBugRepro.java new file mode 100644 index 000000000..e69fdd7b3 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/behavior/BackupTxLogDuplicateBugRepro.java @@ -0,0 +1,155 @@ +package test.eclipse.store.behavior; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +import org.eclipse.serializer.afs.types.AFile; +import org.eclipse.serializer.chars.VarString; +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.serializer.io.XIO; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageTransactionsAnalysis; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.configuration.Customer; +import test.eclipse.store.configuration.CustomerGenerator; + +/** + * Minimal reproduction of an Eclipse Store backup-handler bug. + *

+ * Symptom: after housekeeping which creates a new head file, the continuous + * backup transactions log contains exactly one CREATION entry MORE than the + * live transactions log -- 26 bytes (one {@code LENGTH_COMMON_NUMBERED} + * entry) over the live size. The duplicate entry is byte-identical to the + * preceding entry (same timestamp, same file number, fileLength=0). Data + * files ({@code channel_X_N.dat}) remain byte-identical between live and + * backup; only the {@code transactions_X.sft} log diverges. + *

+ * Reproduction: + *

+ *

+ * Suspected cause: race between + * {@code StorageFileWriterBackupping.writeTransactionEntryCreate} (which + * enqueues a {@code CopyItem} into the backup queue) and + * {@code StorageBackupHandler.synchronizeTransactionFile} (which deletes + * the backup transactions file and copies the whole live transactions + * log when sizes mismatch). When synchronize fires before the backup + * queue drains the pending {@code CopyItem}, the copy-all path picks up + * the new entry, and then the backup queue thread later appends the same + * 26 bytes again -> duplicate. + *

+ * The original {@code DeletionDirectoryBehaviorTest#continuousBackupReflectsCurrentLiveStorageNotArchive} + * surfaces the same bug under a larger workload with a deletion directory. + * This test trims the scenario to the smallest configuration that still + * triggers it. + */ +@Disabled("https://github.com/microstream-one/internal/issues/52") +class BackupTxLogDuplicateBugRepro +{ + + private static final int CUSTOMER_COUNT = 75; + + private static final ByteSize DATA_FILE_MIN = ByteSize.New(1, ByteUnit.KiB); + private static final ByteSize DATA_FILE_MAX = ByteSize.New(2, ByteUnit.KiB); + + //@Test + @RepeatedTest(10) + void backupTransactionsLogMatchesLive(@TempDir final Path workDir) throws IOException + { + final Path storageDir = workDir.resolve("storage"); + final Path backupDir = workDir.resolve("backup"); + + // Session 1: fresh storage with a continuous backup, write enough data so that + // multiple data files are produced and at least one becomes a non-head file. + final EmbeddedStorageConfigurationBuilder builder = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(storageDir.toString()) + .setBackupDirectory(backupDir.toString()) + .setChannelCount(1) + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX); + + EmbeddedStorageManager mgr = builder + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(new ArrayList<>(CustomerGenerator.generateCustomers(CUSTOMER_COUNT))) + .start(); + mgr.storeRoot(); + mgr.shutdown(); + + // Session 2: orphan everything, force GC + file check. Housekeeping + // consolidation creates a new head file and writes a CREATION entry. + mgr = builder + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager() + .start(); + mgr.setRoot(new ArrayList()); + mgr.storeRoot(); + mgr.issueFullGarbageCollection(); + mgr.issueFullFileCheck(); + mgr.shutdown(); + + final Path liveTx = storageDir.resolve("channel_0/transactions_0.sft"); + final Path backupTx = backupDir.resolve("channel_0/transactions_0.sft"); + + final long liveLen = Files.size(liveTx); + final long backupLen = Files.size(backupTx); + + // Uncomment to print both transaction logs in human-readable form to see + // exactly which entry diverges between live and backup. Same output that + // org.eclipse.store.storage.util.MainUtilTransactionFileConverter produces. + dumpTransactionLog("LIVE", liveTx); + dumpTransactionLog("BACKUP", backupTx); + + assertEquals(liveLen, backupLen, + "Backup transactions log differs from live: live=" + liveLen + + " backup=" + backupLen + " delta=" + (backupLen - liveLen) + " bytes"); + } + + /** + * Print a parsed transactions log to {@code System.out}. Mirrors the logic of + * {@link org.eclipse.store.storage.util.MainUtilTransactionFileConverter} so that + * {@code .sft} files can be inspected without rebuilding the storage tooling. + */ + @SuppressWarnings("unused") + private static void dumpTransactionLog(final String label, final Path path) + { + final AFile file = NioFileSystem.New().ensureFile(XIO.Path(path.toString())); + final VarString vs = VarString.New("=== " + label + " " + path + " ===").lf(); + StorageTransactionsAnalysis.EntryAssembler.assembleHeader(vs, "\t").lf(); + StorageTransactionsAnalysis.Logic.parseFile(file, vs).lf(); + System.out.println(vs.toString()); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/behavior/DeletionDirectoryBehaviorTest.java b/integration-tests/src/test/java/test/eclipse/store/behavior/DeletionDirectoryBehaviorTest.java new file mode 100644 index 000000000..f065c4fff --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/behavior/DeletionDirectoryBehaviorTest.java @@ -0,0 +1,690 @@ +package test.eclipse.store.behavior; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageConfiguration; +import org.eclipse.store.storage.util.StorageObjectRestorer; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.configuration.Customer; +import test.eclipse.store.configuration.CustomerGenerator; + +/** + * Behavioral tests for the deletion-directory configuration. + *

+ * The classic configuration tests only assert that some file appears + * under the deletion directory after housekeeping. These tests verify what the + * feature is actually expected to deliver in production: + *

+ */ +class DeletionDirectoryBehaviorTest +{ + + private static final int CUSTOMER_COUNT = 200; + + private static final ByteSize DATA_FILE_MIN = ByteSize.New(1, ByteUnit.KiB); + private static final ByteSize DATA_FILE_MAX = ByteSize.New(2, ByteUnit.KiB); + + @Test + @Disabled("https://github.com/microstream-one/internal/issues/56") + void recoverableFromDeletionDirectorySingleChannel(@TempDir final Path workDir) throws IOException + { + assertRecoverableThroughDeletionDirectory(workDir, 1); + } + + @Test + @Disabled("https://github.com/microstream-one/internal/issues/56") + void recoverableFromDeletionDirectoryMultiChannel(@TempDir final Path workDir) throws IOException + { + assertRecoverableThroughDeletionDirectory(workDir, 4); + } + + @Test + void noCustomerIsLostAfterHousekeeping(@TempDir final Path workDir) throws IOException + { + // Crash-safety invariant under the documented design (StorageFileManager.deleteFile + // writes a transaction-log entry BEFORE physical delete, and StorageFileWriter + // returns early if the rescue-into-deletion-dir succeeded so the source file is never + // blindly deleted): for every persisted object, after housekeeping the object must + // still be locatable -- either in active storage, or in the deletion directory, or both. + // + // The test runs with the *default* file-cleanup settings (no head-file cleanup, default + // minimum-use-ratio) so that the assertion reflects real-world configurations rather + // than the aggressive settings used by the recoverable-* tests. + + final Path storageDir = workDir.resolve("storage"); + final Path deletionDir = workDir.resolve("deleted"); + final int channelCount = 1; + + final List kept = CustomerGenerator.generateCustomers(5); + final List ephemerals = CustomerGenerator.generateCustomers(CUSTOMER_COUNT); + final long[] allOids = new long[kept.size() + ephemerals.size()]; + + runDefaultFreshSession(storageDir, deletionDir, channelCount, new ArrayList<>(kept), mgr -> + { + mgr.storeRoot(); + int idx = 0; + for (final Customer c : kept) { + allOids[idx++] = mgr.persistenceManager().lookupObjectId(c); + } + for (final Customer c : ephemerals) { + mgr.store(c); + allOids[idx++] = mgr.persistenceManager().lookupObjectId(c); + } + }); + + runDefaultReopenSession(storageDir, deletionDir, channelCount, mgr -> + { + mgr.issueFullGarbageCollection(); + mgr.issueFullFileCheck(); + }); + + // Phase A: which OIDs are still in active storage? Open storage, try to load each + // one. lookupObject returns the cached instance; getObject falls back to a loader + // that scans live data files. + final Set foundInActive = new HashSet<>(); + runDefaultReopenSession(storageDir, deletionDir, channelCount, mgr -> + { + for (final long oid : allOids) { + try { + final Object o = mgr.persistenceManager().getObject(oid); + if (o != null) { + foundInActive.add(oid); + } + } catch (final Exception ignored) { + // not in active storage -- that's expected for some + } + } + }); + + // Phase B: storage closed; for OIDs not found in active storage, check deletion-dir + // via the restorer. The restorer scans only deletion-dir files. + final StorageConfiguration restorerConfig = defaultConfigBuilder(storageDir, deletionDir, channelCount) + .createEmbeddedStorageFoundation() + .getConfiguration(); + final StorageObjectRestorer restorer = new StorageObjectRestorer.Default(restorerConfig); + + final List lost = new ArrayList<>(); + for (final long oid : allOids) { + if (foundInActive.contains(oid)) { + continue; + } + if (!restorer.restoreObject(oid)) { + lost.add(oid); + } + } + + assertTrue(lost.isEmpty(), + "Some object ids were lost -- not present in active storage and not recoverable from " + + "deletion directory either. Count=" + lost.size() + " oids=" + lost); + } + + @Test + @Disabled("https://github.com/microstream-one/internal/issues/52") + void continuousBackupReflectsCurrentLiveStorageNotArchive(@TempDir final Path workDir) + throws IOException, NoSuchAlgorithmException, InterruptedException + { + // Continuous backup behavior: when housekeeping rescues a file from the main + // storage into the main deletion-directory, the corresponding backup file is + // processed by StorageBackupHandler.deleteFile -- which calls + // StorageFileWriter.deleteFile with the BACKUP file provider. The backup + // provider, when configured via EmbeddedStorageConfigurationBuilder.setBackupDirectory(...), + // has NO deletion-directory of its own, so the backup copy is physically removed. + // + // In other words: continuous backup is a live-state mirror, not an archive. + // To preserve rescued files in the backup as well, the backup file provider + // would need its own deletion-directory configured via lower-level API. + + final Path storageDir = workDir.resolve("storage"); + final Path deletionDir = workDir.resolve("deleted"); + final Path backupDir = workDir.resolve("backup"); + + final List customers = CustomerGenerator.generateCustomers(CUSTOMER_COUNT); + + runFreshSessionWithBackup(storageDir, deletionDir, backupDir, new ArrayList<>(customers), mgr -> + { + mgr.storeRoot(); + }); + + runReopenSessionWithBackup(storageDir, deletionDir, backupDir, mgr -> + { + mgr.setRoot(new ArrayList()); + mgr.storeRoot(); + mgr.issueFullGarbageCollection(); + mgr.issueFullFileCheck(); + }); + + // Continuous backup processes its queue asynchronously. After shutdown the + // drain should be complete, but Files.walk might race with FS sync on some + // platforms. Wait for backup-dir state to stabilize. +// Awaitility.await() +// .atMost(Duration.ofSeconds(10)) +// .pollInterval(Duration.ofMillis(100)) +// .until(() -> hashesEqual(collectFileHashes(storageDir), collectFileHashes(backupDir))); + + Thread.sleep(10_000); + System.out.println(backupDir); + System.out.println(storageDir); + + assertTrue(Files.isDirectory(backupDir), "Backup directory was not created"); + assertTrue(Files.isDirectory(backupDir.resolve("channel_0")), + "Backup directory has no channel_0 -- continuous backup did not mirror channels"); + + // 1) Main deletion-dir DID receive rescued files. + assertFalse(collectFileHashes(deletionDir).isEmpty(), + "Sanity check failed: main deletion directory is empty after housekeeping"); + + // 2) Backup directory does NOT have a deletion subdirectory by default -- + // confirms that backup file provider has no deletion-directory configured. + assertFalse(Files.isDirectory(backupDir.resolve("deleted")), + "Backup directory unexpectedly contains a 'deleted' subdirectory; " + + "expected default config to give backup no deletion-directory of its own"); + + // 3) Backup mirrors current live storage byte-for-byte. + final Set liveHashes = collectFileHashes(storageDir); + final Set backupHashes = collectFileHashes(backupDir); + if (!liveHashes.equals(backupHashes)) { + System.err.println("=== DIAG: live vs backup mismatch ==="); + System.err.println("--- live ---"); + dumpFilesWithMeta(storageDir); + System.err.println("--- backup ---"); + dumpFilesWithMeta(backupDir); + System.err.println("--- deletion-dir ---"); + dumpFilesWithMeta(deletionDir); + + final Path debugDir = Path.of("/tmp/dd-debug/" + System.currentTimeMillis()); + Files.createDirectories(debugDir); + final Path liveTx = storageDir.resolve("channel_0/transactions_0.sft"); + final Path backupTx = backupDir.resolve("channel_0/transactions_0.sft"); + if (Files.exists(liveTx)) { + Files.copy(liveTx, debugDir.resolve("live_transactions_0.sft")); + } + if (Files.exists(backupTx)) { + Files.copy(backupTx, debugDir.resolve("backup_transactions_0.sft")); + } + System.err.println("Transaction logs copied to: " + debugDir); + } + assertEquals(liveHashes, backupHashes, + "Backup is not a byte-identical mirror of current live storage. " + + "Live=" + liveHashes.size() + " backup=" + backupHashes.size()); + + // 4) Files rescued into the main deletion-dir are NOT preserved in the backup. + // (In default config the backup just drops them.) + final Set deletedHashes = collectFileHashes(deletionDir); + final Set intersect = new HashSet<>(deletedHashes); + intersect.retainAll(backupHashes); + assertTrue(intersect.isEmpty(), + "Backup unexpectedly retained files that were rescued into the main " + + "deletion-directory. Overlap=" + intersect.size()); + } + + @Test + void issueFullBackupExportsLiveStorageNotDeletedFiles(@TempDir final Path workDir) + throws IOException, NoSuchAlgorithmException + { + // issueFullBackup is a one-shot snapshot of the current live state (it + // calls exportChannels under the hood). It should not export the + // deletion-directory contents -- those are housekeeping rescue copies, not + // live data. + + final Path storageDir = workDir.resolve("storage"); + final Path deletionDir = workDir.resolve("deleted"); + final Path fullBackupDir = workDir.resolve("fullbackup"); + Files.createDirectories(fullBackupDir); + + runFreshSession(storageDir, deletionDir, 1, + new ArrayList<>(CustomerGenerator.generateCustomers(CUSTOMER_COUNT)), + EmbeddedStorageManager::storeRoot); + + runReopenSession(storageDir, deletionDir, 1, mgr -> + { + mgr.setRoot(new ArrayList()); + mgr.storeRoot(); + mgr.issueFullGarbageCollection(); + mgr.issueFullFileCheck(); + + final ADirectory backupADir = NioFileSystem.New().ensureDirectoryPath(fullBackupDir.toString()); + mgr.persistenceManager().objectRegistry(); // touch to ensure connection initialized + mgr.issueFullBackup(backupADir); + }); + + assertTrue(Files.isDirectory(fullBackupDir.resolve("channel_0")), + "Full backup did not produce a channel_0 directory"); + assertFalse(Files.isDirectory(fullBackupDir.resolve("deleted")), + "Full backup unexpectedly contains a 'deleted' subdirectory"); + + // Hashes in full backup should equal current live storage; deletion-dir hashes + // should NOT appear in the full backup. + final Set liveHashes = collectFileHashes(storageDir); + final Set fullBackupHashes = collectFileHashes(fullBackupDir); + final Set deletedHashes = collectFileHashes(deletionDir); + + final Set intersect = new HashSet<>(fullBackupHashes); + intersect.retainAll(deletedHashes); + assertTrue(intersect.isEmpty(), + "issueFullBackup unexpectedly exported files that were rescued into the " + + "deletion-directory. Overlap=" + intersect.size()); + + // The full backup may carry the type dictionary in addition to data files, + // so it can be a strict superset of live data files but must not contain + // rescued files. + assertTrue(fullBackupHashes.containsAll(intersectionWith(liveHashes, fullBackupHashes)), + "Full backup is missing live storage data files"); + } + + @Test + void liveStorageRemainsUsableAfterHousekeepingMovesFilesAside(@TempDir final Path workDir) + throws IOException, NoSuchAlgorithmException + { + final Path storageDir = workDir.resolve("storage"); + final Path deletionDir = workDir.resolve("deleted"); + + final List kept = CustomerGenerator.generateCustomers(5); + + // Session 1: 5 customers reachable from the root + many ephemeral + // customers stored as orphans (unreachable from the root, garbage on GC). + runFreshSession(storageDir, deletionDir, 1, new ArrayList<>(kept), mgr -> + { + mgr.storeRoot(); + for (int i = 0; i < CUSTOMER_COUNT; i++) { + mgr.store(CustomerGenerator.generateNewCustomer()); + } + }); + + // Session 2: explicit GC + file check moves orphan-bearing files aside. + runReopenSession(storageDir, deletionDir, 1, mgr -> + { + mgr.issueFullGarbageCollection(); + mgr.issueFullFileCheck(); + }); + + assertTrue(Files.isDirectory(deletionDir), "Deletion directory was not created"); + assertFalse(collectFileHashes(deletionDir).isEmpty(), + "No file moved into the deletion directory; the test did not exercise housekeeping"); + + // Session 3: reopen and verify the kept customers are still loadable + // from live storage with their original field values. + runReopenSession(storageDir, deletionDir, 1, mgr -> + { + @SuppressWarnings("unchecked") final List loaded = (List) mgr.root(); + assertNotNull(loaded, "Root was not loaded after reopen"); + assertEquals(kept.size(), loaded.size(), + "Live root size changed after housekeeping moved files aside"); + for (int i = 0; i < kept.size(); i++) { + assertCustomerEquals(kept.get(i), loaded.get(i), "kept[" + i + "]"); + } + }); + } + + /////////////////////////////////////////////////////////////////////////// + // scenario // + ///////////// + + /** + * Round-trip scenario: store customers, capture their object ids, orphan them + * via housekeeping, then verify that {@link StorageObjectRestorer} can + * recover each one from the deletion directory and that the recovered + * customers carry the original field values. + */ + private static void assertRecoverableThroughDeletionDirectory( + final Path workDir, + final int channelCount + ) + throws IOException + { + final Path storageDir = workDir.resolve("storage"); + final Path deletionDir = workDir.resolve("deleted"); + + final List originals = CustomerGenerator.generateCustomers(CUSTOMER_COUNT); + final long[] oids = new long[originals.size()]; + + // Session 1: store every customer as part of the root list, capture OIDs. + runFreshSession(storageDir, deletionDir, channelCount, new ArrayList<>(originals), mgr -> + { + mgr.storeRoot(); + for (int i = 0; i < originals.size(); i++) { + final long oid = mgr.persistenceManager().lookupObjectId(originals.get(i)); + assertNotEquals(0L, oid, + "Customer at index " + i + " has no object id after storeRoot"); + oids[i] = oid; + } + }); + + // Session 2: orphan everything and force housekeeping to move the + // now-garbage data files into the deletion directory. Head file cleanup + // is enabled and the use-ratio threshold is raised so even sparsely-used + // files are dissolved -- otherwise some customers would survive in the + // active head file and would not be recoverable from deletion-dir. + runReopenSession(storageDir, deletionDir, channelCount, mgr -> + { + mgr.setRoot(new ArrayList()); + mgr.storeRoot(); + mgr.issueFullGarbageCollection(); + mgr.issueFullFileCheck(); + }); + + // Restorer must run on a stopped storage. Build a fresh foundation just + // to extract a configuration that points at the same directories. + final StorageConfiguration restorerConfig = configBuilder(storageDir, deletionDir, channelCount) + .createEmbeddedStorageFoundation() + .getConfiguration(); + + final StorageObjectRestorer restorer = new StorageObjectRestorer.Default(restorerConfig); + + int recovered = 0; + final List failedIndexes = new ArrayList<>(); + for (int i = 0; i < oids.length; i++) { + if (restorer.restoreObject(oids[i])) { + recovered++; + } else { + failedIndexes.add(i); + } + } + + assertEquals(originals.size(), recovered, + "Some customers could not be recovered from deletion directory: indexes " + failedIndexes); + + // Session 3: reopen and look up each customer by its original OID. Field + // values must equal the originals -- this is the actual proof that the + // data preserved in the deletion directory is the data we intended. + runReopenSession(storageDir, deletionDir, channelCount, mgr -> + { + for (int i = 0; i < originals.size(); i++) { + final Object loaded = mgr.persistenceManager().getObject(oids[i]); + assertNotNull(loaded, + "Customer with oid " + oids[i] + " (index " + i + ") not loadable after restore"); + assertTrue(loaded instanceof Customer, + "Loaded object for oid " + oids[i] + " is not a Customer: " + loaded.getClass()); + assertCustomerEquals(originals.get(i), (Customer) loaded, "originals[" + i + "]"); + } + }); + } + + /////////////////////////////////////////////////////////////////////////// + // helpers // + + /// ///////// + + private static void assertCustomerEquals(final Customer expected, final Customer actual, final String label) + { + assertEquals(expected.getFirstName(), actual.getFirstName(), label + ".firstName"); + assertEquals(expected.getSecondName(), actual.getSecondName(), label + ".secondName"); + assertEquals(expected.getStreet(), actual.getStreet(), label + ".street"); + assertEquals(expected.getCity(), actual.getCity(), label + ".city"); + } + + private static EmbeddedStorageConfigurationBuilder configBuilder( + final Path storageDir, + final Path deletionDir, + final int channelCount + ) + { + return EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(storageDir.toString()) + .setDeletionDirectory(deletionDir.toString()) + .setChannelCount(channelCount) + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX) + .setDataFileMinimumUseRatio(0.99) + .setDataFileCleanupHeadFile(true); + } + + /** + * Builder with only deletion-dir + small data file sizes set, so housekeeping + * uses default thresholds (head-file cleanup off, default minimum-use-ratio). + * Used by {@link #noCustomerIsLostAfterHousekeeping} to test the + * never-lose-an-object invariant under realistic configurations. + */ + private static EmbeddedStorageConfigurationBuilder defaultConfigBuilder( + final Path storageDir, + final Path deletionDir, + final int channelCount + ) + { + return EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(storageDir.toString()) + .setDeletionDirectory(deletionDir.toString()) + .setChannelCount(channelCount) + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX); + } + + private static void runFreshSession( + final Path storageDir, + final Path deletionDir, + final int channelCount, + final Object initialRoot, + final Consumer action + ) + { + final EmbeddedStorageManager mgr = configBuilder(storageDir, deletionDir, channelCount) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(initialRoot) + .start(); + try { + action.accept(mgr); + } finally { + mgr.shutdown(); + } + } + + private static void runReopenSession( + final Path storageDir, + final Path deletionDir, + final int channelCount, + final Consumer action + ) + { + final EmbeddedStorageManager mgr = configBuilder(storageDir, deletionDir, channelCount) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager() + .start(); + try { + action.accept(mgr); + } finally { + mgr.shutdown(); + } + } + + private static void runDefaultFreshSession( + final Path storageDir, + final Path deletionDir, + final int channelCount, + final Object initialRoot, + final Consumer action + ) + { + final EmbeddedStorageManager mgr = defaultConfigBuilder(storageDir, deletionDir, channelCount) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(initialRoot) + .start(); + try { + action.accept(mgr); + } finally { + mgr.shutdown(); + } + } + + private static EmbeddedStorageConfigurationBuilder backupConfigBuilder( + final Path storageDir, + final Path deletionDir, + final Path backupDir, + final int channelCount + ) + { + return EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(storageDir.toString()) + .setDeletionDirectory(deletionDir.toString()) + .setBackupDirectory(backupDir.toString()) + .setChannelCount(channelCount) + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX) + .setDataFileMinimumUseRatio(0.99) + .setDataFileCleanupHeadFile(true); + } + + private static void runFreshSessionWithBackup( + final Path storageDir, + final Path deletionDir, + final Path backupDir, + final Object initialRoot, + final Consumer action + ) + { + final EmbeddedStorageManager mgr = backupConfigBuilder(storageDir, deletionDir, backupDir, 1) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(initialRoot) + .start(); + try { + action.accept(mgr); + } finally { + mgr.shutdown(); + } + } + + private static void runReopenSessionWithBackup( + final Path storageDir, + final Path deletionDir, + final Path backupDir, + final Consumer action + ) + { + final EmbeddedStorageManager mgr = backupConfigBuilder(storageDir, deletionDir, backupDir, 1) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager() + .start(); + try { + action.accept(mgr); + } finally { + mgr.shutdown(); + } + } + + private static boolean hashesEqual(final Set a, final Set b) + { + return a.equals(b); + } + + private static Set intersectionWith(final Set a, final Set b) + { + final Set result = new HashSet<>(a); + result.retainAll(b); + return result; + } + + private static void runDefaultReopenSession( + final Path storageDir, + final Path deletionDir, + final int channelCount, + final Consumer action + ) + { + final EmbeddedStorageManager mgr = defaultConfigBuilder(storageDir, deletionDir, channelCount) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager() + .start(); + try { + action.accept(mgr); + } finally { + mgr.shutdown(); + } + } + + private static void dumpFilesWithMeta(final Path dir) + throws IOException, NoSuchAlgorithmException + { + if (!Files.exists(dir)) { + System.err.println(" (does not exist: " + dir + ")"); + return; + } + final MessageDigest md = MessageDigest.getInstance("SHA-256"); + try (Stream stream = Files.walk(dir)) { + final List files = stream + .filter(Files::isRegularFile) + .sorted(Comparator.naturalOrder()) + .toList(); + for (final Path f : files) { + final byte[] data = Files.readAllBytes(f); + md.reset(); + final byte[] digest = md.digest(data); + final StringBuilder sb = new StringBuilder(digest.length * 2); + for (final byte b : digest) { + sb.append(String.format("%02x", b)); + } + System.err.println(" " + dir.relativize(f) + " size=" + data.length + " sha=" + sb); + } + } + } + + private static Set collectFileHashes(final Path dir) + throws IOException, NoSuchAlgorithmException + { + if (!Files.exists(dir)) { + return new HashSet<>(); + } + + final Set hashes = new HashSet<>(); + final MessageDigest md = MessageDigest.getInstance("SHA-256"); + + try (Stream stream = Files.walk(dir)) { + final List files = stream + .filter(Files::isRegularFile) + .sorted(Comparator.naturalOrder()) + .toList(); + + for (final Path f : files) { + final byte[] data = Files.readAllBytes(f); + if (data.length == 0) { + continue; + } + md.reset(); + final byte[] digest = md.digest(data); + final StringBuilder sb = new StringBuilder(digest.length * 2); + for (final byte b : digest) { + sb.append(String.format("%02x", b)); + } + hashes.add(sb.toString()); + } + } + + return hashes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/cache/CacheEvictTest.java b/integration-tests/src/test/java/test/eclipse/store/cache/CacheEvictTest.java new file mode 100644 index 000000000..c8308d8ed --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/cache/CacheEvictTest.java @@ -0,0 +1,92 @@ +package test.eclipse.store.cache; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; +import javax.cache.spi.CachingProvider; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.eclipse.serializer.reference.LazyReferenceManager; +import org.eclipse.store.cache.types.CacheConfiguration; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +public class CacheEvictTest +{ + + private static final String CACHED_VALUE = "one"; + private static final int KEY = 1; + + @Test + void cacheItemEvicted() throws InterruptedException + { + CachingProvider provider = Caching.getCachingProvider(); + CacheManager cacheManager = provider.getCacheManager(); + + + MutableConfiguration configuration = new MutableConfiguration() + .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, 500))) + .setStoreByValue(true); + + Cache cache = cacheManager.createCache("jCache1", configuration); + cache.put(KEY, CACHED_VALUE); + + Assertions.assertEquals(CACHED_VALUE, cache.get(KEY)); + + Thread.sleep(700); // More then the 1 sec expiration policy + + Assertions.assertNull(cache.get(KEY)); + } + + @Test + void cacheItemEvictedWithStorage(@TempDir Path tempdir) throws InterruptedException + { + EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempdir); + + CachingProvider provider = Caching.getCachingProvider(); + CacheManager cacheManager = provider.getCacheManager(); + + + CacheConfiguration configuration = CacheConfiguration + .Builder(Integer.class, String.class, "jCache", storageManager) + .expiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, 500))) + .build(); + + Cache cache = cacheManager.createCache("jCache", configuration); + cache.put(KEY, CACHED_VALUE); + + Assertions.assertEquals(CACHED_VALUE, cache.get(KEY)); + + Thread.sleep(700); // More than the 1 sec expiration policy + + Assertions.assertNull(cache.get(KEY)); + + storageManager.close(); + + LazyReferenceManager referenceManager = LazyReferenceManager.get(); + referenceManager.stop(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Export.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Export.java new file mode 100644 index 000000000..d8c09feb9 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Export.java @@ -0,0 +1,97 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.serializer.collections.types.XSequence; +import org.eclipse.serializer.util.cql.CQL; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.*; + +public class Export +{ + + public static XSequence export(final EmbeddedStorageManager storage, final String target) + { + + final NioFileSystem fileSystem = NioFileSystem.New(); + + final String fileSuffix = "bin"; + final StorageConnection connection = storage.createConnection(); + final StorageEntityTypeExportStatistics exportResult = connection.exportTypes( + new StorageEntityTypeExportFileProvider.Default( + fileSystem.ensureDirectoryPath(target), + fileSuffix + ), + typeHandler -> true // export all, customize if necessary + ); + final XSequence exportFiles = CQL + .from(exportResult.typeStatistics().values()) + .project(s -> Paths.get(s.file().toPathString())) + .execute(); + + return exportFiles; + } + + public static void convertToCSV(final EmbeddedStorageManager storage, final XSequence files, final String target) + { + + final NioFileSystem fileSystem = NioFileSystem.New(); + + final StorageDataConverterTypeBinaryToCsv converter = + new StorageDataConverterTypeBinaryToCsv.UTF8( + StorageDataConverterCsvConfiguration.defaultConfiguration(), + new StorageEntityTypeConversionFileProvider.Default( + fileSystem.ensureDirectoryPath(target), + "csv" + ), + storage.typeDictionary(), + null, // no type name mapping + 4096, // read buffer size + 4096 // write buffer size + ); + + files.forEach(file -> converter.convertDataFile( + fileSystem.ensureFile(file).tryUseReading() + )); + } + + public static void exportAsCSV(final EmbeddedStorageManager storage, final Path targetDir) + { + + System.out.println("deleting target dir before export " + targetDir.toString()); + + try { + Utils.deleteAll(targetDir); + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return; + } + + final Path binDir = targetDir.resolve("bin"); + final XSequence exports = export(storage, binDir.toString()); + + final Path csvDir = targetDir.resolve("csv"); + convertToCSV(storage, exports, csvDir.toString()); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListAutoUnloadTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListAutoUnloadTest.java new file mode 100644 index 000000000..c83f712e8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListAutoUnloadTest.java @@ -0,0 +1,232 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyArrayListAutoUnloadTest +{ + + + @Test + void removedSegments(@TempDir final Path path) + { + + LazyArrayList list = new LazyArrayList<>(3, new LazySegmentUnloader.Default(1)); + for (int i = 0; i < 27; i++) { + list.add("Entry_" + i); + } + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(list, path)) { + + + storage.setRoot(list); + storage.storeRoot(); + //System.out.println("total segments: " + countSegments(list)); + assertEquals(9, countSegments(list)); + + list.remove(0); + list.remove(0); + list.remove(0); + list.remove(0); + list.remove(0); + list.remove(0); + list.get(20); + + storage.store(list); + //System.out.println("total segment after remove: " + countSegments(list)); + assertEquals(7, countSegments(list)); + + //System.out.println("loaded segments after remove: \n" + getLoadedSegments(list).size() + "\n"); + assertEquals(1, getLoadedSegments(list).size()); + + } + } + + + @Test + void devTest(@TempDir final Path path) + { + + LazyArrayList list = new LazyArrayList<>(5); + + for (int i = 0; i < 27; i++) { + list.add("Entry_" + i); + } + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(list, path)) { + + assertFalse(list.contains("not existing")); + //System.out.println("loaded segments after contains (not found): \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + assertTrue(list.contains("Entry_18")); + //System.out.println("loaded segments after contains (found) : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.add("Entry_28"); + storage.store(list); + //System.out.println("loaded segments after add : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.add(9, "Entry_add_9"); + storage.store(list); + //System.out.println("\n loaded segments after add index : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.set(19, "Entry_set_18"); + storage.store(list); + //System.out.println("loaded segments after set : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.get(11); + storage.store(list); + //System.out.println("loaded segments after get : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.forEach(e -> e.concat("Hallo")); + storage.store(list); + //System.out.println("loaded segments after forEach : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.remove("Entry_13"); + storage.store(list); + //System.out.println("loaded segments after remove1 : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.remove("Entry_28"); + storage.store(list); + //System.out.println("loaded segments after remove2 : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.remove(list.size() - 1); + list.remove(list.size() - 1); + storage.store(list); + //System.out.println("loaded segments after remove3 : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + list.remove(14); + list.remove(15); + list.remove(16); + list.remove(17); + //System.out.println("loaded segments after remove4 : \n" + getLoadedSegments(list) + "\n"); + storage.store(list); + //System.out.println("loaded segments after remove4 : \n" + getLoadedSegments(list) + "\n"); + list.get(0); + //System.out.println("loaded segments after remove4 : \n" + getLoadedSegments(list) + "\n"); + assertLoadedSegment(list); + + } + + + //System.out.println("--------------------------------- reloading storage ---------------------------------"); + + try (final EmbeddedStorageManager storageReloaded = EmbeddedStorage.start(path)) { + + @SuppressWarnings("unchecked") + LazyArrayList listReloaded = (LazyArrayList) storageReloaded.root(); + //listReloaded.forEach(e -> System.out.println(e)); + //System.out.println("loaded segments after forEach : \n" + getLoadedSegments(listReloaded) + "\n"); + assertLoadedSegment(list); + + } + } + + + static void unloadAll(LazyArrayList list) + { + + final Iterable.Segment> segments = list.segments(); + segments.forEach(LazyArrayList.Segment::unloadSegment); + + } + + + void assertLoadedSegment(LazyArrayList list) + { + + int expected = 0; + + try { + final Field unloader = list.getClass() + .getDeclaredField("unloader"); + unloader.setAccessible(true); + final Field load = unloader.get(list) + .getClass() + .getDeclaredField("desiredLoadCount"); + load.setAccessible(true); + final Object ul = unloader.get(list); + expected = load.getInt(ul); + + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + + assertTrue(getLoadedSegments(list).size() <= expected); + } + + void assertNoLoadedSegment(LazyArrayList list) + { + assertTrue(getLoadedSegments(list).size() == 0); + } + + static int countSegments(LazyArrayList list) + { + + final AtomicInteger count = new AtomicInteger(); + + list.segments() + .forEach(s -> count.incrementAndGet()); + + return count.get(); + } + + static List.Segment> getLoadedSegments(LazyArrayList list) + { + + final Iterable.Segment> segments = list.segments(); + + final List.Segment> loadedSegments = new ArrayList<>(); + segments.forEach(s -> { + if (s.isLoaded()) { + loadedSegments.add(s); + } + }); + + return loadedSegments; + } + + static EmbeddedStorageManager startStorage(final Path path) + { + final EmbeddedStorageManager storage = EmbeddedStorage + .Foundation(path) + .start(); + return storage; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListIterateLoadedFirstTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListIterateLoadedFirstTest.java new file mode 100644 index 000000000..38cd00a43 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListIterateLoadedFirstTest.java @@ -0,0 +1,258 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyArrayListIterateLoadedFirstTest +{ + + @Test + void concurrentModificationNext(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 8)) { + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + final Iterator iter = list.loadedFirstIterator(); + list.add(new ListEntry("unexpected")); + + assertThrows(ConcurrentModificationException.class, () -> iter.next()); + } + } + + @Disabled + @Test + void concurrentModificationRemove(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 8)) { + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + final Iterator iter = list.loadedFirstIterator(); + iter.next(); + list.add(new ListEntry("unexpected")); + + assertThrows(ConcurrentModificationException.class, () -> iter.remove()); + + } + } + + + @Test + void emptyList(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 0)) { + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + final Iterator iter = list.loadedFirstIterator(); + assertFalse(iter.hasNext()); + + } + } + + @Test + void RemoveSome(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 3, 67)) { + + + final List referenceList = new ArrayList<>(); + for (int i = 0; i < 67; i++) { + referenceList.add(new ListEntry("Entry-" + i)); + } + referenceList.removeIf(e -> e.id.contains("5")); + + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + final Iterator iter = list.loadedFirstIterator(); + + while (iter.hasNext()) { + final ListEntry item = iter.next(); + if (item.id.contains("5")) iter.remove(); + } + + assertIterableEquals(referenceList, list); + + } + } + + @Test + void removeAll(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 3, 7)) { + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + final Iterator iter = list.loadedFirstIterator(); + + while (iter.hasNext()) { + ListEntry next = iter.next(); + iter.remove(); + } + + assertEquals(0, list.size()); + } + } + + @Test + void iterateAllUnloaded(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 19)) { + + final List referenceList = new ArrayList<>(); + for (int i = 0; i < 19; i++) { + referenceList.add(new ListEntry("Entry-" + i)); + } + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + final Iterator iter = list.loadedFirstIterator(); + this.checkIter(iter, referenceList); + + } + } + + @Test + void iterateAllLoaded(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 19)) { + + final List referenceList = new ArrayList<>(); + for (int i = 0; i < 19; i++) { + referenceList.add(new ListEntry("Entry-" + i)); + } + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + + + list.iterateLazyReferences(l -> { + l.get(); + assertTrue(l.isLoaded()); + }); + + + final Iterator iter = list.loadedFirstIterator(); + this.checkIter(iter, referenceList); + + } + } + + @Test + void iterateSomeLoaded(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 19)) { + + + final List referenceList = new ArrayList<>(); + for (int i = 0; i < 19; i++) { + referenceList.add(new ListEntry("Entry-" + i)); + } + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + final AtomicInteger counter = new AtomicInteger(); + list.iterateLazyReferences(l -> { + if (counter.getAndIncrement() % 2 == 0) { + l.get(); + } + }); + + final Iterator iter = list.loadedFirstIterator(); + this.checkIter(iter, referenceList); + } + } + + @Test + void unloadedAfterIterate(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = createListStorage(path, 5, 19)) { + + final List referenceList = new ArrayList<>(); + for (int i = 0; i < 19; i++) { + referenceList.add(new ListEntry("Entry-" + i)); + } + + @SuppressWarnings("unchecked") + LazyArrayList list = (LazyArrayList) storage.root(); + list.iterateLazyReferences(l -> { + l.get(); + assertTrue(l.isLoaded()); + }); + + final Iterator iter = list.loadedFirstIterator(); + this.checkIter(iter, referenceList); + + AtomicInteger loadedSegments = new AtomicInteger(); + list.iterateLazyReferences(l -> { + if (l.isLoaded()) loadedSegments.incrementAndGet(); + }); + assertEquals(2, loadedSegments.get()); + + } + } + + void checkIter(final Iterator iter, final List reference) + { + while (iter.hasNext()) { + assertTrue(reference.remove(iter.next()), "Iterator returned element that is not in the reference list!"); + } + assertTrue(reference.isEmpty(), "reference list should be empty after removing all iterated elements!"); + } + + static EmbeddedStorageManager createListStorage(final Path path, final int segmentSize, final int entries) + { + final LazyArrayList lazyList = createLazyList(segmentSize, entries); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + } + + return EmbeddedStorage.start(path); + } + + static LazyArrayList createLazyList(final int segmentSize, final int entries) + { + final LazyArrayList lazyList = new LazyArrayList<>(segmentSize); + + for (int i = 0; i < entries; i++) { + lazyList.add(new ListEntry("Entry-" + i)); + } + return lazyList; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListIteratorTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListIteratorTest.java new file mode 100644 index 000000000..9eac0487e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListIteratorTest.java @@ -0,0 +1,74 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class LazyArrayListIteratorTest +{ + + private static Class clazz; + + @BeforeAll + static void init() + { + final Class[] declaredClass = LazyArrayList.class.getDeclaredClasses(); + final Optional> opt = Stream.of(declaredClass).filter(e -> e.getName().equals("one.microstream.collections.lazy.LazyArrayList$Itr")).findFirst(); + if (opt.isPresent()) { + clazz = opt.get(); + } + } + + + static void main(final String[] args) + { + + final int numElements = 255000; + final List list = LazyArrayListPersistenceTest.createLazyList(10, numElements); + } + + + @Test + @Disabled("to long test") + void iterateNext() + { + + final int numElements = 255000; + final LazyArrayList lazyList = LazyArrayListPersistenceTest.createLazyList(10, numElements); + final Iterator iter = lazyList.iterator(); + + //assertInstanceOf(clazz, iter); + + + int counter = 0; + while (iter.hasNext()) { + final ListEntry e = iter.next(); + assertEquals("Entry-" + counter, e.id); + counter++; + } + assertEquals(numElements, counter); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListLoadedTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListLoadedTest.java new file mode 100644 index 000000000..05dd2ba2f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListLoadedTest.java @@ -0,0 +1,86 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.types.PrimitiveTypes; + +public class LazyArrayListLoadedTest +{ + + @TempDir + Path storageLocation; + + @Test + public void lazyArraylistLoadTest() throws InterruptedException + { + + LazyArrayList list = Stream.generate(PrimitiveTypes::new) + .limit(100_000) + .collect(Collectors.toCollection(LazyArrayList::new)); + + PrimitiveTypes types = new PrimitiveTypes(); + types.fillSampleData(); + list.add(50, types); + + AtomicInteger isLoaded = new AtomicInteger(0); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(list, storageLocation)) { + + + list.iterateLazyReferences(i -> i.clear()); + + list.iterateLazyReferences(i -> { + if (i.isLoaded()) { + isLoaded.incrementAndGet(); + } + } + ); + + + list.remove(60); + + list.iterateLazyReferences(i -> { + if (i.isLoaded()) { + isLoaded.incrementAndGet(); + } + } + ); + + + Assertions.assertEquals(1, isLoaded.get()); + } + } + + private static void printMemory() + { + Long memoryAmount = Runtime.getRuntime() + .totalMemory() - Runtime.getRuntime() + .freeMemory(); + //System.out.println(memoryAmount); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListModCountTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListModCountTest.java new file mode 100644 index 000000000..bb76b3d2d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListModCountTest.java @@ -0,0 +1,214 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.exceptions.IllegalAccessRuntimeException; +import org.eclipse.serializer.reflect.XReflect; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + + +@Disabled +public class LazyArrayListModCountTest +{ + + private static Field modCount; + + @BeforeAll + static void initTests() + { + modCount = XReflect.setAccessible(LazyArrayList.class, XReflect.getAnyField(LazyArrayList.class, "modCount")); + } + + @Test + void add() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 0); + final int initialModCount = getField_int(modCount, lal); + + lal.add(new ListEntry("new entry")); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void addIndex() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 2); + final int initialModCount = getField_int(modCount, lal); + + lal.add(1, new ListEntry("new entry")); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void addAll() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 2); + final int initialModCount = getField_int(modCount, lal); + + lal.addAll(List.of(new ListEntry("new entry 1"), new ListEntry("new entry 2"), new ListEntry("new entry 3"), new ListEntry("new entry 4"))); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void addAllIndex() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 2); + final int initialModCount = getField_int(modCount, lal); + + lal.addAll(1, List.of(new ListEntry("new entry 1"), new ListEntry("new entry 2"), new ListEntry("new entry 3"), new ListEntry("new entry 4"))); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void clear() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 4); + final int initialModCount = getField_int(modCount, lal); + + lal.clear(); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void consolidate_nothingTodo() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 4); + final int initialModCount = getField_int(modCount, lal); + + lal.consolidate(); + + assertEquals(initialModCount, getField_int(modCount, lal)); + } + + @Test + void consolidate() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(2, 6); + lal.removeAll(List.of(new ListEntry("Entry-1"), new ListEntry("Entry-2"), new ListEntry("Entry-3"), new ListEntry("Entry-4"))); + final int initialModCount = getField_int(modCount, lal); + + lal.consolidate(); + + assertEquals(initialModCount + 2, getField_int(modCount, lal)); + } + + @Test + void remove() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 4); + final int initialModCount = getField_int(modCount, lal); + + lal.remove(new ListEntry("Entry-1")); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void removeIndex() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 4); + final int initialModCount = getField_int(modCount, lal); + + lal.remove(2); + + assertEquals(initialModCount + 1, getField_int(modCount, lal)); + } + + @Test + void removeAll() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 7); + final int initialModCount = getField_int(modCount, lal); + + lal.removeAll(List.of(new ListEntry("Entry-1"), new ListEntry("Entry-2"), new ListEntry("Entry-3"), new ListEntry("Entry-4"))); + + assertTrue(initialModCount < getField_int(modCount, lal)); + } + + @Test + void retainAll() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 7); + final int initialModCount = getField_int(modCount, lal); + + lal.retainAll(List.of(new ListEntry("Entry-1"), new ListEntry("Entry-2"), new ListEntry("Entry-3"), new ListEntry("Entry-3"))); + + assertTrue(initialModCount < getField_int(modCount, lal)); + } + + @Test + void removeIf() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 7); + final int initialModCount = getField_int(modCount, lal); + + lal.removeIf(e -> (e.id.contains("2") || e.id.contains("4"))); + + assertTrue(initialModCount < getField_int(modCount, lal)); + } + + @Test + void iteratorRemove() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 7); + final int initialModCount = getField_int(modCount, lal); + + final Iterator iter = lal.iterator(); + iter.next(); + while (iter.hasNext()) { + iter.next(); + iter.remove(); + } + + assertTrue(initialModCount < getField_int(modCount, lal)); + } + + @Test + void iteratorAdd() + { + final LazyArrayList lal = LazyArrayListPersistenceTest.createLazyList(4, 7); + final int initialModCount = getField_int(modCount, lal); + + final ListIterator iter = lal.listIterator(6); + iter.add(new ListEntry("ListEntry")); + + assertTrue(initialModCount < getField_int(modCount, lal)); + } + + public static int getField_int(final Field f, final Object obj) throws IllegalAccessRuntimeException + { + try { + return f.getInt(obj); + } catch (final IllegalAccessException e) { + throw new IllegalAccessRuntimeException(e); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListPersistenceTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListPersistenceTest.java new file mode 100644 index 000000000..cdc00fcc8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListPersistenceTest.java @@ -0,0 +1,496 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyArrayListPersistenceTest +{ + + + @Test + void nullElements(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + lazyList.add(null); + lazyList.add(null); + lazyList.add(null); + lazyList.add(null); + + assertThrows(NullPointerException.class, () -> lazyList.addAll(null)); + + final ListEntry element7 = lazyList.get(7); + assertNull(element7); + lazyList.remove(7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + + storage.setRoot(lazyList); + storage.storeRoot(); + } + + loadAndCompare(path, lazyList, l -> { + assertNull(l.get(7)); + assertEquals(10, l.size()); + }); + } + + @Test + void lastIndexOf(@TempDir final Path path) + { + + final LazyArrayList lazyList = createLazyList(3, 7); + lazyList.add(0, new ListEntry("added")); + lazyList.add(4, new ListEntry("added")); + lazyList.add(6, new ListEntry("added")); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(6, l.lastIndexOf(new ListEntry("added"))); + }); + } + + @Test + void lastIndexOfNotFound(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(-1, l.lastIndexOf(new ListEntry("added"))); + }); + } + + + @Test + void indexOf(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(4, l.indexOf(new ListEntry("Entry-4"))); + }); + } + + @Test + void indexOfNotFound(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(-1, l.indexOf(new ListEntry("not in list"))); + }); + } + + + @Test + void get(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(new ListEntry("Entry-4"), l.get(4)); + }); + } + + @Test + void contains(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + } + + loadAndCompare(path, lazyList, l -> { + assertTrue(l.contains(new ListEntry("Entry-4")), "LazyArrayList does not contain expected object new String(\"Entry-4\")"); + }); + } + + @Test + void consolidate(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + lazyList.add(1, new ListEntry("added")); + lazyList.add(4, new ListEntry("added")); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.consolidate(); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + //after consolidate the list should have 3 segments with 3 elements each; + assertEquals(3, l.getSegmentCount()); + l.segments() + .forEach(s -> { + assertEquals(3, s.getSize()); + }); + }); + } + + @Test + void clear(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + lazyList.iterateLazyReferences(l -> l.clear()); + lazyList.clear(); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertTrue(l.isEmpty()); + }); + } + + + @Test + void addAllAtIndex(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + lazyList.iterateLazyReferences(l -> l.clear()); + + final List newEntries = Arrays.asList( + new ListEntry("New entry 1"), new ListEntry("New entry 2"), + new ListEntry("New entry 3"), new ListEntry("New entry 4"), + new ListEntry("New entry 5"), new ListEntry("New entry 6")); + + lazyList.addAll(4, newEntries); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(13, l.size()); + }); + } + + @Test + void addAllAtEnd(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + final List newEntries = Arrays.asList( + new ListEntry("New entry 1"), new ListEntry("New entry 2"), + new ListEntry("New entry 3"), new ListEntry("New entry 4"), + new ListEntry("New entry 5"), new ListEntry("New entry 6")); + + lazyList.addAll(newEntries); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(13, l.size()); + }); + + } + + @Test + void unloadPartialTest(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + + lazyList.add(new ListEntry("not stored entry")); + + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.segments() + .forEach(s -> { + if (s.getOffset() >= 6) { + assertTrue(s.isLoaded(), "Expected dirty references NOT to be unloaded!"); + } else { + assertFalse(s.isLoaded(), "Expected non dirty references to be unloaded!"); + } + }); + + } + + //The loaded list must not be the same as the modified but not stored one. + assertThrows(org.opentest4j.AssertionFailedError.class, () -> loadAndCompare(path, lazyList, null)); + + } + + @Test + void unloadAllTest(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 14); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.iterateLazyReferences(l -> + assertFalse(l.isLoaded(), "Expected all lazy references to be unloaded!") + ); + + } + + loadAndCompare(path, lazyList, null); + } + + @Test + void setElementInList(@TempDir final Path path) + { + + final LazyArrayList lazyList = createLazyList(5, 12); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.set(3, new ListEntry("lazyList.set")); + storage.store(lazyList); + + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(new ListEntry("lazyList.set"), l.get(3)); + }); + } + + /** + * Remove a single element from a persisted list using its index + * + * @param path + */ + @Test + void removeIndex(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(5, 12); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.remove(3); + storage.store(lazyList); + + } + + loadAndCompare(path, lazyList, null); + } + + @Test + void removeObject(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(5, 12); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.remove(new ListEntry("Entry-6")); + storage.store(lazyList); + + } + loadAndCompare(path, lazyList, null); + } + + @Test + void RemoveAll(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + final List newEntries = Arrays.asList( + new ListEntry("Entry-1"), + new ListEntry("Entry-3"), + new ListEntry("Entry-5")); + + lazyList.removeAll(newEntries); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(4, l.size()); + }); + } + + @Test + void RemoveIf(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + final List newEntries = Arrays.asList( + new ListEntry("Entry-1"), + new ListEntry("Entry-3"), + new ListEntry("Entry-5")); + + lazyList.removeIf(newEntries::contains); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(4, l.size()); + }); + } + + @Test + void RetainAll(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(3, 7); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + final List newEntries = Arrays.asList( + new ListEntry("Entry-1"), + new ListEntry("Entry-3"), + new ListEntry("Entry-5")); + + lazyList.retainAll(newEntries); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(3, l.size()); + }); + } + + @Test + void addToEmptyList(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(5, 12); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + } + loadAndCompare(path, lazyList, null); + } + + @Test + void addAtIndex(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(5, 12); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + + lazyList.add(4, new ListEntry("Entry add at 4")); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + assertEquals(new ListEntry("Entry add at 4"), l.get(4)); + }); + } + + @Test + void toObjectArray(@TempDir final Path path) + { + final LazyArrayList lazyList = createLazyList(5, 12); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + + lazyList.iterateLazyReferences(l -> l.clear()); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + final Object[] array = l.toArray(); + for (int i = 0; i < array.length; i++) { + assertEquals(lazyList.get(i), array[i]); + } + }); + } + + @Test + void toTypedArray(@TempDir final Path path) + { + + final LazyArrayList lazyList = createLazyList(5, 12); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(lazyList, path)) { + lazyList.iterateLazyReferences(l -> l.clear()); + storage.store(lazyList); + } + + loadAndCompare(path, lazyList, l -> { + final Object[] array = l.toArray(new ListEntry[0]); + for (int i = 0; i < array.length; i++) { + assertEquals(lazyList.get(i), array[i]); + } + }); + } + + static LazyArrayList loadAndCompare(final Path path, + final LazyArrayList lazyList, + final Consumer> f + ) + { + + final LazyArrayList lazyListReloaded = new LazyArrayList<>(); + try (final EmbeddedStorageManager storageReloaded = EmbeddedStorage.start(lazyListReloaded, path)) { + + assertNotSame(lazyList, lazyListReloaded, "Lists are reference equal"); + assertEquals(lazyList.size(), lazyListReloaded.size(), "Size mismatch!"); + assertEquals(lazyList.getMaxSegmentSize(), lazyListReloaded.getMaxSegmentSize(), "getMaxSegmentSize mismatch!"); + assertEquals(lazyList.getSegmentCount(), lazyListReloaded.getSegmentCount(), "getSegmentCount mismatch!"); + assertIterableEquals(lazyList, lazyListReloaded); + + if (f != null) + f.accept(lazyListReloaded); + } + + return lazyListReloaded; + } + + static LazyArrayList createLazyList(final int segmentSize, final int entries) + { + final LazyArrayList lazyList = new LazyArrayList<>(segmentSize); + + for (int i = 0; i < entries; i++) { + lazyList.add(new ListEntry("Entry-" + i)); + } + return lazyList; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListSmokePersistenceTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListSmokePersistenceTest.java new file mode 100644 index 000000000..23d5ade63 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListSmokePersistenceTest.java @@ -0,0 +1,420 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.ListIterator; +import java.util.function.UnaryOperator; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyArrayListSmokePersistenceTest +{ + + + @TempDir + Path location; + + @Test + public void addItem() + { + LazyArrayList list = new LazyArrayList<>(); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(list, location)) { + + assertTrue(list.isEmpty()); + assertEquals(0, list.size()); + assertTrue(list.add(1)); + storage.store(list); + LazyArrayList reloaded = (LazyArrayList) storage.root(); + + assertEquals(1, reloaded.size()); + assertEquals(1, reloaded.get(0)); + + assertTrue(list.addAll(list)); + assertEquals(2, list.size()); + + assertTrue(list.add(null)); + + storage.store(list); + list = (LazyArrayList) storage.root(); + assertEquals(3, list.size()); + } + + LazyArrayList copy = new LazyArrayList<>(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(copy, location)) { + assertEquals(3, list.size()); + } + } + + @Test + public void isEmpty() + { + LazyArrayList list = new LazyArrayList<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, location)) { + assertTrue(list.isEmpty()); + } + } + + @Test + public void contains() + { + String s = "Hello, i would to be a great object"; + + LazyArrayList list = new LazyArrayList<>(); + list.add(s); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, location)) { + } + + LazyArrayList copy = new LazyArrayList<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(copy, location)) { + assertTrue(copy.contains(s)); + } + + } + + + @Test + public void iterator() + { + String s = "Hello, i would to be a great object"; + + LazyArrayList list = new LazyArrayList<>(); + list.add(s); + list.add(s); + list.add(s); + + LazyArrayList copy = new LazyArrayList<>(); + + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + ListIterator listIterator = pair.getKey() + .listIterator(); + + int i = 0; + + while (listIterator.hasNext()) { + i++; + listIterator.next(); + } + + assertEquals(3, i); + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + + } + } + + @Test + public void toArray() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(list, location)) { + + } + + List copy = new LazyArrayList<>(); + try (EmbeddedStorageManager storage = EmbeddedStorage.start(copy, location)) { + Object[] intArray = copy.toArray(); + + + Object[] expectIntArray = {1, 2, 3}; + assertArrayEquals(expectIntArray, intArray); + + } + + } + + @Test + public void toArrayWithType() + { + LazyArrayList list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + LazyArrayList copy = pair.getKey(); + + Integer[] intArray = copy.toArray(new Integer[0]); + + Integer[] expectIntArray = {1, 2, 3}; + assertArrayEquals(expectIntArray, intArray); + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + + } + + } + + @Test + public void remove() + { + LazyArrayList list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + LazyArrayList copy = pair.getKey(); + + copy.remove(2); + + assertEquals(2, copy.size()); + + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + } + + } + + @Test + public void containsAll() + { + LazyArrayList list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + assertTrue(list.containsAll(list2)); + + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + LazyArrayList copy = pair.getKey(); + + assertTrue(copy.containsAll(list2)); + + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + } + } + + @Test + public void addAllIndex() + { + LazyArrayList list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + LazyArrayList copy; + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + copy = pair.getKey(); + + assertTrue(copy.containsAll(list2)); + + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + } + + assertTrue(copy.addAll(2, list2)); + + + List list3 = new LazyArrayList<>(); + list3.add(1); + list3.add(2); + list3.add(1); + list3.add(2); + list3.add(3); + list3.add(3); + + assertIterableEquals(list3, copy); + } + + @Test + public void removeAllCollection() + { + LazyArrayList list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + + LazyArrayList copy; + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + copy = pair.getKey(); + + assertTrue(copy.containsAll(list2)); + + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + } + + assertTrue(copy.removeAll(list2)); + + assertTrue(copy.isEmpty()); + } + + @Test + public void retainAll() + { + LazyArrayList list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(3); + + + LazyArrayList copy; + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + copy = pair.getKey(); + + copy.iterateLazyReferences(e -> e.get()); + + + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + } + + assertTrue(copy.retainAll(list2)); + + List resultList = new LazyArrayList<>(); + resultList.add(1); + resultList.add(3); + + assertIterableEquals(resultList, copy); + } + + + @Test + public void replaceAllUnaryOperator() + { + class Op implements UnaryOperator + { + public String apply(String str) + { + return str.toUpperCase(); + } + } + + List list = new LazyArrayList<>(); + list.add("Java"); + list.add("JavaScript"); + //System.out.println("Contents of the list: " + list); + list.replaceAll(new Op()); + + List expectedList = new LazyArrayList<>(); + expectedList.add("JAVA"); + expectedList.add("JAVASCRIPT"); + + assertIterableEquals(expectedList, list); + } + + @Test + public void sort() + { + + Comparator valueComparator = (Integer o1, Integer o2) -> o1 - o2; + + LazyArrayList list = new LazyArrayList<>(); + list.add(3); + list.add(2); + list.add(1); + + LazyArrayList copy; + ImmutablePair, EmbeddedStorageManager> pair = null; + try { + pair = Util.storeAndLoadList(list, location); + copy = pair.getKey(); + copy.sort(valueComparator); + + pair.getValue().store(copy); + + } finally { + if ((pair != null) && (pair.getValue() != null)) { + pair.getValue() + .shutdown(); + } + } + + + List resultList = new LazyArrayList<>(); + resultList.add(1); + resultList.add(2); + resultList.add(3); + + assertIterableEquals(resultList, copy); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListUnloadingTests.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListUnloadingTests.java new file mode 100644 index 000000000..032be64c9 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/LazyArrayListUnloadingTests.java @@ -0,0 +1,110 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyArrayListUnloadingTests +{ + + @Test + void UnloadStored(@TempDir final Path path) + { + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + LazyArrayList list = createLazyList(22); + + storage.setRoot(list); + storage.storeRoot(); + + list.iterateLazyReferences(l -> l.clear()); + list.iterateLazyReferences(l -> assertFalse(l.isLoaded())); + + } + } + + @Test + void UnloadNotStored(@TempDir final Path path) + { + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + LazyArrayList list = createLazyList(44); + + storage.setRoot(list); + + list.iterateLazyReferences(l -> l.clear()); + list.iterateLazyReferences(l -> assertTrue(l.isLoaded())); + } + } + + @Test + void UnloadNotStored_reloadedStorage(@TempDir final Path path) + { + + try (final EmbeddedStorageManager storageReloaded = createReloadedStorageWithList(path)) { + @SuppressWarnings("unchecked") + LazyArrayList listReloaded = (LazyArrayList) storageReloaded.root(); + listReloaded.iterateLazyReferences(l -> assertFalse(l.isLoaded())); + + listReloaded.add("New Entry"); + + listReloaded.iterateLazyReferences(l -> l.clear()); + + listReloaded.segments() + .forEach(s -> { + if (s.isModified()) { + assertTrue(s.isLoaded(), "expected modified segment to be loaded!"); + } else { + assertFalse(s.isLoaded(), "expected unmodified segment to be unloaded!"); + } + }); + + storageReloaded.store(listReloaded); + listReloaded.iterateLazyReferences(l -> l.clear()); + listReloaded.iterateLazyReferences(l -> assertFalse(l.isLoaded())); + } + } + + + static EmbeddedStorageManager createReloadedStorageWithList(final Path path) + { + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + LazyArrayList list = createLazyList(44); + storage.setRoot(list); + storage.storeRoot(); + } + return EmbeddedStorage.start(path); + } + + + private static LazyArrayList createLazyList(final int size) + { + LazyArrayList list = new LazyArrayList<>(); + + for (int i = 0; i < size; i++) { + list.add("Entry " + i); + } + return list; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/ListEntry.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/ListEntry.java new file mode 100644 index 000000000..d1de510f1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/ListEntry.java @@ -0,0 +1,56 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Objects; + +public class ListEntry +{ + + String id; + + public ListEntry(final String id) + { + super(); + this.id = id; + } + + @Override + public int hashCode() + { + return Objects.hash(this.id); + } + + @Override + public boolean equals(final Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (this.getClass() != obj.getClass()) + return false; + final ListEntry other = (ListEntry) obj; + return Objects.equals(this.id, other.id); + } + + @Override + public String toString() + { + return "ListEntry{" + + "id='" + id + '\'' + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/PersistenceTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/PersistenceTest.java new file mode 100644 index 000000000..ca0023e40 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/PersistenceTest.java @@ -0,0 +1,76 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class PersistenceTest +{ + + + @Test + public void storeAndReload(@TempDir final Path path) + { + + LazyArrayList myLazyList = new LazyArrayList<>(3); + for (int i = 0; i < 10; i++) { + myLazyList.add("Entry " + i); + } + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(myLazyList, path)) { + } + + LazyArrayList reloadedList; + try (final EmbeddedStorageManager storageReloaded = EmbeddedStorage.start(path)) { + + reloadedList = (LazyArrayList) storageReloaded.root(); + + assertIterableEquals(myLazyList, reloadedList); + + reloadedList.add(7, "AddedEntry"); + storageReloaded.store(reloadedList); + } + + + LazyArrayList reloadedList2; + try (final EmbeddedStorageManager storageReloaded2 = EmbeddedStorage.start(path)) { + + reloadedList2 = (LazyArrayList) storageReloaded2.root(); + + assertIterableEquals(reloadedList, reloadedList2); + + reloadedList2.add("nochnadd"); + reloadedList2.consolidate(); + storageReloaded2.store(reloadedList2); + } + + try (final EmbeddedStorageManager storageReloaded3 = EmbeddedStorage.start(path)) { + @SuppressWarnings("unchecked") + LazyArrayList reloadedList3 = (LazyArrayList) storageReloaded3.root(); + assertIterableEquals(reloadedList2, reloadedList3); + + } + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/SpliteratorTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/SpliteratorTest.java new file mode 100644 index 000000000..bb28ba7c6 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/SpliteratorTest.java @@ -0,0 +1,179 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Spliterator; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class SpliteratorTest +{ + + @Test + void createSpliterator() + { + + final ArrayList arrayList = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + arrayList.add("entry " + i); + } + + final Spliterator arrayListSpliterator = arrayList.spliterator(); + final Spliterator splitArrayList = arrayListSpliterator.trySplit(); + + + final LazyArrayList lazyList = new LazyArrayList<>(4); + for (int i = 0; i < 20; i++) { + lazyList.add("entry " + i); + } + + final Spliterator lazySpliterator = lazyList.segmentSpliterator(); + final Spliterator splitLazy = lazySpliterator.trySplit(); + final Spliterator splitLazy2 = lazySpliterator.trySplit(); + + final AtomicInteger counter = new AtomicInteger(); + lazySpliterator.forEachRemaining(s -> counter.getAndAdd(1)); + splitLazy.forEachRemaining(s -> counter.getAndAdd(1)); + splitLazy2.forEachRemaining(s -> counter.getAndAdd(1)); + + + assertEquals(20, counter.get(), "total iterated elements"); + } + + @Test + void createSpliteratorRecursive() + { + + final int maxSegmentSize = 4; + final int numElements = 20; + + final LazyArrayList lazyList = new LazyArrayList<>(maxSegmentSize); + + for (int i = 0; i < numElements; i++) { + lazyList.add(new ListEntry("Entry " + i)); + } + + final List> spliterators = this.splitAll(lazyList.spliterator()); + + assertEquals(5, spliterators.size(), "expected to get one spliterator for each segment (" + numElements / maxSegmentSize + ")"); + } + + + @Test + void lateBinding() + { + + final int maxSegmentSize = 4; + final int numElements = 20; + + final LazyArrayList lazyList = new LazyArrayList<>(maxSegmentSize); + + for (int i = 0; i < numElements - 10; i++) { + lazyList.add(new ListEntry("Entry " + i)); + } + + final Spliterator spliterator = lazyList.spliterator(); + + for (int i = 10; i < numElements; i++) { + lazyList.add(new ListEntry("Entry " + i)); + } + + final List> spliterators = this.splitAll(spliterator); + + assertEquals(5, spliterators.size(), "expected to get one spliterator for each segment (" + numElements / maxSegmentSize + ")"); + } + + @Test + void readStreamAndCloseTest(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + final LazyArrayList lazyList = Util.createLazyList(3, 25); + + storage.setRoot(lazyList); + storage.storeRoot(); + lazyList.iterateLazyReferences(l -> l.clear()); + lazyList.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long countFours; + try (final Stream stream = lazyList.parallelStream()) { + countFours = stream.filter(e -> e.id.contains("4")) + .count(); + } + + assertEquals(3, countFours); + + AtomicInteger loadedSegments = new AtomicInteger(); + lazyList.iterateLazyReferences(l -> { + if (l.isLoaded()) loadedSegments.incrementAndGet(); + }); + assertEquals(0, loadedSegments.get()); + } + } + + @Test + @Disabled + void deleteStreamTest(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + final LazyArrayList lazyList = Util.createLazyList(3, 25); + + storage.setRoot(lazyList); + storage.storeRoot(); + lazyList.iterateLazyReferences(l -> l.clear()); + lazyList.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + { + final long countTwos = lazyList.parallelStream() + .filter(e -> e.id.contains("4")) + .count(); + assertEquals(3, countTwos); + } + + System.gc(); + + lazyList.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + } + } + + + private List> splitAll(final Spliterator spliterator) + { + final List> spliterators = new ArrayList<>(); + final Spliterator split = spliterator.trySplit(); + + if (split != null) { + spliterators.addAll(this.splitAll(spliterator)); + spliterators.addAll(this.splitAll(split)); + } else { + spliterators.add(spliterator); + } + + return spliterators; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/TwoStoragesTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/TwoStoragesTest.java new file mode 100644 index 000000000..2947c1a49 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/TwoStoragesTest.java @@ -0,0 +1,125 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Path; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.persistence.exceptions.PersistenceException; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +//@Disabled("Will not work beacuse auf Lazy.Default also does not support this") +public class TwoStoragesTest +{ + + @TempDir + Path location; + + private static PrintStream originalErr; + + @BeforeAll + static void setupErr() + { + originalErr = System.err; + System.setErr(new PrintStream(new OutputStream() + { + @Override + public void write(int b) + { + // Discard output + } + })); + } + + @AfterAll + static void restoreErr() + { + System.setErr(originalErr); + } + + + @Test + public void removeTest(@TempDir final Path path) + { + LazyArrayList list = new LazyArrayList<>(); + + list.add("some text"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, path)) { + //no op + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, path)) { + + list.remove(0); + } + } + + + public static class MyRoot + { + Lazy lazy; + + public MyRoot(final String content) + { + super(); + this.lazy = Lazy.Reference(content); + } + + } + + @Test + public void saveDefaultLazySecondTimeTest(@TempDir final Path secondLocation) + { + final MyRoot myRoot = new MyRoot("Hello World"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(myRoot, this.location)) { + //no op + } + + myRoot.lazy.clear(); + + assertThrows(PersistenceException.class, () -> EmbeddedStorage.start(myRoot, secondLocation)); + + } + + public static LazyArrayList generateList(final Integer count) + { + return generateList(count, 0); + } + + public static LazyArrayList generateList(final Integer count, final int keyStart) + { + + LazyArrayList list = new LazyArrayList<>(); + + for (int i = 0; i < count; i++) { + list.add("Hello World " + i); + } + + return list; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Util.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Util.java new file mode 100644 index 000000000..87f648f96 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Util.java @@ -0,0 +1,93 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + +public class Util +{ + + + static ImmutablePair, EmbeddedStorageManager> storeAndLoadList(LazyArrayList list, Path location) + { + try (EmbeddedStorageManager storage = startStorage(list, location)) { + + } + + LazyArrayList copy = new LazyArrayList<>(); + EmbeddedStorageManager storage = startStorage(copy, location); + + return ImmutablePair.of(copy, storage); + + } + + static EmbeddedStorageManager startStorage(Path path) + { + final EmbeddedStorageManager storage = EmbeddedStorage + .Foundation(path) + .start(); + return storage; + } + +// static EmbeddedStorageManager startStorage( LazyArrayList root, final Path path) { +// final EmbeddedStorageManager storage = EmbeddedStorage +// .Foundation(path) +// .registerTypeHandler(BinaryHandlerLazyArrayList.New()) +// .start(root); +// return storage; +// } + + static EmbeddedStorageManager startStorage(LazyArrayList root, final Path path) + { + final EmbeddedStorageManager storage = EmbeddedStorage + .Foundation(path) + .start(root); + return storage; + } + + static LazyArrayList createLazyList(final int segmentSize, final int entries) + { + final LazyArrayList lazyList = new LazyArrayList<>(segmentSize); + + for (int i = 0; i < entries; i++) { + lazyList.add(new ListEntry("Entry-" + i)); + } + return lazyList; + } + + static LazyArrayList loadAndCompare(final LazyArrayList lazyList, EmbeddedStorageManager storage) + { + + LazyArrayList lazyListReloaded; + + lazyListReloaded = (LazyArrayList) storage.root(); + + assertAll("load nad compare list", + () -> assertEquals(lazyList.size(), lazyListReloaded.size(), "Size mismatch!"), + () -> assertEquals(lazyList.getMaxSegmentSize(), lazyListReloaded.getMaxSegmentSize(), "getMaxSegmentSize mismatch!"), + () -> assertEquals(lazyList.getSegmentCount(), lazyListReloaded.getSegmentCount(), "getSegmentCount mismatch!"), + () -> assertIterableEquals(lazyList, lazyListReloaded) + ); + + return lazyListReloaded; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Utils.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Utils.java new file mode 100644 index 000000000..cb1768e73 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/Utils.java @@ -0,0 +1,66 @@ +package test.eclipse.store.collections.lazy.arraylist; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; + +public class Utils +{ + + public static void deleteAll(final Path path) throws IOException + { + if (path.toFile().exists()) { + Files.walk(path) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + public static void deleteAll(final String path) throws IOException + { + deleteAll(Paths.get(path)); + } + + public static void pressAnyKeyToContinue() + { + System.out.println("Press Enter key to continue..."); + try { + System.in.read(); + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + public static void pressAnyKeyToContinue(final String string) + { + System.out.println(string + " Press Enter key to continue..."); + try { + System.in.read(); + } catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Generator.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Generator.java new file mode 100644 index 000000000..f68349eba --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Generator.java @@ -0,0 +1,55 @@ +package test.eclipse.store.collections.lazy.arraylist.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.List; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; + +import net.datafaker.Faker; + +public class Generator +{ + + static final int PERSON_COUNT = 200; + + static Faker faker = new Faker(); + + public static List generatePersons() + { + return generatePersons(PERSON_COUNT, new LazySegmentUnloader.Default()); + } + + public static List generatePersons(int count, LazySegmentUnloader unloader) + { + return generatePersons(count, unloader, 1000); + } + + public static List generatePersons(int count, LazySegmentUnloader unloader, int MaxSegmentSize) + { + + List persons = new LazyArrayList<>(1000, unloader); + for (int i = 0; i < count; i++) { + Person person = new Person(); + person.setFirstname(faker.name().firstName()); + person.setLastname(faker.name().lastName()); + person.setMail(faker.internet().emailAddress()); + person.setIp(faker.internet().ipV4Address()); + persons.add(person); + } + return persons; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/MoreThreadTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/MoreThreadTest.java new file mode 100644 index 000000000..64160fbdd --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/MoreThreadTest.java @@ -0,0 +1,79 @@ +package test.eclipse.store.collections.lazy.arraylist.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +public class MoreThreadTest +{ + + @TempDir + Path location; + + private EmbeddedStorageManager storageManager; + + @AfterEach + void stopStorage() + { + if (storageManager != null) { + if (storageManager.isActive()) { + storageManager.shutdown(); + } + } + } + + @Test + void manual_unloader_test_tryUnload_false() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(20_000, new LazySegmentUnloader.Default()); + + List copyPersons = new ArrayList<>(personList); + + storageManager = EmbeddedStorage.start(personList, location); + + Assertions.assertIterableEquals(copyPersons, personList); + + int threadCount = 5; + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + Thread thread = new Thread(() -> { + personList.parallelStream() + .map(p -> p.toString()) + .collect(Collectors.toList()); + Assertions.assertIterableEquals(copyPersons, personList); + }); + threads[i] = thread; + thread.start(); + } + + for (int i = 0; i < threadCount; i++) { + threads[i].join(); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Person.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Person.java new file mode 100644 index 000000000..23cdadb58 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Person.java @@ -0,0 +1,97 @@ +package test.eclipse.store.collections.lazy.arraylist.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class Person +{ + private String firstname; + private String lastname; + private String mail; + private String gender; + private String ip; + private String value1; + private String value2; + + public String getFirstname() + { + return firstname; + } + + public void setFirstname(String firstname) + { + this.firstname = firstname; + } + + public String getLastname() + { + return lastname; + } + + public void setLastname(String lastname) + { + this.lastname = lastname; + } + + public String getMail() + { + return mail; + } + + public void setMail(String mail) + { + this.mail = mail; + } + + public String getGender() + { + return gender; + } + + public void setGender(String gender) + { + this.gender = gender; + } + + public String getIp() + { + return ip; + } + + public void setIp(String ip) + { + this.ip = ip; + } + + public String getValue1() + { + return value1; + } + + public void setValue1(String value1) + { + this.value1 = value1; + } + + public String getValue2() + { + return value2; + } + + public void setValue2(String value2) + { + this.value2 = value2; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Unloader.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Unloader.java new file mode 100644 index 000000000..599463987 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/arraylist/unload/Unloader.java @@ -0,0 +1,205 @@ +package test.eclipse.store.collections.lazy.arraylist.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +public class Unloader +{ + + @TempDir + Path location; + + @Test + void unloadTestTimedUnloader() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(8_000, new LazySegmentUnloader.Timed(100)); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + List collect = personList.stream() + .map(p -> p.getLastname()) + .collect(Collectors.toList()); + + Thread.sleep(210); + personList.get(0); + + Assertions.assertEquals(1, getLoadedSegments(personList).size()); + } + } + + @Test + void unloadTestDefaultUnloader() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(8_000, new LazySegmentUnloader.Default()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + List collect = personList.stream() + .map(p -> p.getLastname()) + .collect(Collectors.toList()); + + Thread.sleep(210); + personList.get(0); + + Assertions.assertEquals(2, getLoadedSegments(personList).size()); + } + } + + @Test + void unloadTestNeverUnloader() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(8000, new LazySegmentUnloader.Never(), 100); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + List collect = personList.stream() + .map(p -> p.getLastname()) + .collect(Collectors.toList()); + + Thread.sleep(100); + personList.get(0); + + Assertions.assertEquals(8, getLoadedSegments(personList).size()); + } + } + + + static List.Segment> getLoadedSegments(LazyArrayList list) + { + + final Iterable.Segment> segments = list.segments(); + + final List.Segment> loadedSegments = new ArrayList<>(); + segments.forEach(s -> { + if (s.isLoaded()) { + loadedSegments.add(s); + } + }); + + return loadedSegments; + } + + @Test + void unloadTestTimedUnloader_parallelStream() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(8000, new LazySegmentUnloader.Timed(50), 500); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + List collect = personList.parallelStream() + .map(p -> p.getLastname()) + .collect(Collectors.toList()); + + Thread.sleep(110); + personList.get(0); + + Assertions.assertEquals(1, getLoadedSegments(personList).size()); + } + } + + @Test + void streamOf_Test() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(800, new LazySegmentUnloader.Timed(100), 100); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + Stream personStream = personList.stream(); + + personStream.forEach(person -> person.getLastname()); + + Thread.sleep(210); + personList.get(0); + + Assertions.assertEquals(1, getLoadedSegments(personList).size()); + } + } + + @Test + void shuffle_unloader_test() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(1000, new LazySegmentUnloader.Timed(50), 100); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + Collections.shuffle(personList); + storageManager.store(personList); + + Thread.sleep(120); + personList.get(0); + + Assertions.assertEquals(1, getLoadedSegments(personList).size()); + } + } + + @Test + void manual_unloader_test() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(8_000, new LazySegmentUnloader.Timed(50), 500); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + Thread.sleep(110); + personList.tryUnload(true); + + Assertions.assertEquals(0, getLoadedSegments(personList).size()); + + personList.parallelStream() + .map(Object::toString) + .collect(Collectors.toList()); + Assertions.assertTrue(getLoadedSegments(personList).size() > 0); //some are loaded + Thread.sleep(110); + personList.tryUnload(true); + Assertions.assertEquals(0, getLoadedSegments(personList).size()); + } + } + + @Test + void manual_unloader_test_tryUnload_false() throws InterruptedException + { + LazyArrayList personList; + personList = (LazyArrayList) Generator.generatePersons(20_000, new LazySegmentUnloader.Default()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personList, location)) { + + personList.tryUnload(false); + Assertions.assertEquals(2, getLoadedSegments(personList).size()); + personList.tryUnload(true); + Assertions.assertEquals(0, getLoadedSegments(personList).size()); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapLoadTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapLoadTest.java new file mode 100644 index 000000000..ed676e700 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapLoadTest.java @@ -0,0 +1,115 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.serializer.persistence.types.Storer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.TypeRegister; +import test.eclipse.serializer.fixtures.types.LazyData; +import test.eclipse.serializer.fixtures.types.PrimitiveTypes; + +public class HashMapLoadTest +{ + + @TempDir + Path location; + + private final static int COUNT = 100; + + + @Test + //@RepeatedTest(1000) + @Disabled("https://github.com/microstream-one/microstream-private/issues/715") + void lazyHashMap() throws InterruptedException + { + LazyHashMap bigLazyHashMap = new LazyHashMap<>(5, new LazySegmentUnloader.Timed(500)); + Map integerTypeRegisterMap; + for (int i = 0; i < COUNT; i++) { + bigLazyHashMap.put(i, new TypeRegister()); + } + try (EmbeddedStorageManager manager = EmbeddedStorage.start(bigLazyHashMap, location)) { + bigLazyHashMap.forEach((integer, typeRegister) -> { + typeRegister.fillSampleDate(); + Storer eagerStorer = manager.createEagerStorer(); + eagerStorer.store(typeRegister); + eagerStorer.commit(); + }); + manager.store(bigLazyHashMap); + integerTypeRegisterMap = Map.copyOf(bigLazyHashMap); + } + + LazyHashMap loadedMap; + try (EmbeddedStorageManager manager = EmbeddedStorage.start(location)) { + loadedMap = (LazyHashMap) manager.root(); + System.out.println(loadedMap.size()); + + Assertions.assertIterableEquals(integerTypeRegisterMap.entrySet(), loadedMap.entrySet()); + } + + } + + @Test + void lazyHashMapPrimitive() throws InterruptedException + { + LazyHashMap bigLazyHashMap = new LazyHashMap<>(5, new LazySegmentUnloader.Timed(500)); + Map integerTypeRegisterMap = new HashMap<>(); + + for (int i = 0; i < COUNT; i++) { + bigLazyHashMap.put(i, new LazyData()); + } + try (EmbeddedStorageManager manager = EmbeddedStorage.start(bigLazyHashMap, location)) { + bigLazyHashMap.forEach((integer, primitiveTypes) -> { + primitiveTypes.fillSampleData(); + Storer eagerStorer = manager.createEagerStorer(); + eagerStorer.store(primitiveTypes); + eagerStorer.commit(); + }); + manager.store(bigLazyHashMap); + + bigLazyHashMap.entrySet().forEach(entry -> { + integerTypeRegisterMap.put(entry.getKey(), entry.getValue().getLazy().get()); + }); + + } + + LazyHashMap loadedMap; + Map loadedMapWithoutLazy = new HashMap<>(); + try (EmbeddedStorageManager manager = EmbeddedStorage.start(location)) { + loadedMap = (LazyHashMap) manager.root(); + + + for (Map.Entry entry : loadedMap.entrySet()) { + loadedMapWithoutLazy.put(entry.getKey(), entry.getValue().getLazy().get()); + } + + } + + Assertions.assertIterableEquals(integerTypeRegisterMap.entrySet(), loadedMapWithoutLazy.entrySet()); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapSmokeTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapSmokeTest.java new file mode 100644 index 000000000..8674add99 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapSmokeTest.java @@ -0,0 +1,558 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyCollection; +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySet; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.types.PrimitiveTypes; + +public class HashMapSmokeTest +{ + + @TempDir + Path location; + + @Test + public void sizeTest() + { + LazyHashMap map = Util.generateMap(100); + + assertEquals(100, map.size()); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertEquals(100, map.size()); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + assertEquals(100, map1.size()); + } + } + + @Test + public void isEmpty() + { + LazyHashMap map = new LazyHashMap<>(); + + assertTrue(map.isEmpty()); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertTrue(map.isEmpty()); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + assertTrue(map1.isEmpty()); + } + } + + @Test + public void containsKey() + { + LazyHashMap map = Util.generateMap(100); + map.put(null, null); + + assertTrue(map.containsValue(null)); + assertTrue(map.containsKey(5)); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertTrue(map.containsKey(5)); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + assertTrue(map1.containsKey(5)); + } + } + + @Test + public void containsValueNull() + { + LazyHashMap map = new LazyHashMap<>(); + map.put(null, null); + + assertTrue(map.containsKey(null)); + + assertTrue(map.containsValue(null)); + assertFalse(map.containsKey(5)); + } + + + @Test + public void containsValue() + { + String value = "Hi, i am a great value test sentence"; + LazyHashMap map = new LazyHashMap<>(); + + map.put(1, value); + assertTrue(map.containsValue(value)); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertTrue(map.containsValue(value)); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + assertTrue(map1.containsValue(value)); + } + } + + @Test + public void getTest() + { + String value = "Hi, i am a great value test sentence"; + LazyHashMap map = new LazyHashMap<>(); + + map.put(1, value); + assertEquals(value, map.get(1)); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertEquals(value, map.get(1)); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + assertEquals(value, map1.get(1)); + } + } + + @Test + public void removeTest() + { + String value = "Hi, i am a great value test sentence"; + LazyHashMap map = new LazyHashMap<>(); + + map.put(1, value); + assertEquals(value, map.get(1)); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertEquals(value, map.get(1)); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + assertEquals(value, map1.get(1)); + map1.remove(1); + assertTrue(map1.isEmpty()); + } + } + + @Test + void remoteNullValueTest() + { + LazyHashMap map = Util.generateMap(100); + map.put(101, null); + map.put(102, null); + assertEquals(102, map.size()); + map.remove(102); + assertEquals(101, map.size()); + } + + + @Test + void remoteNullValueHashMapTest() + { + HashMap map = Util.generateHashMap(100); + map.put(101, null); + map.put(102, null); + assertEquals(102, map.size()); + map.remove(102); + assertEquals(101, map.size()); + } + + @Test + public void putALLtoOneStorage() + { + LazyHashMap map = Util.generateMap(100); + + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + LazyHashMap secondMap = Util.generateMap(100, 100); + + map.putAll(secondMap); + storageManager.store(map); + } + + map = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertEquals(200, map.size()); + } + + } + + /** + * Just test to prove behavior with no-lazy collections. + * + * @param secondLocation Junit feature to provide private folder in temp directory + */ + @Test + public void lazyTwoStorageTest(@TempDir Path secondLocation) + { + + ArrayList> lazyList = Stream.generate(() -> { + PrimitiveTypes type1 = new PrimitiveTypes(); + type1.fillSampleData(); + return Lazy.Reference(type1); + }) + .limit(1000) + .collect(Collectors.toCollection(ArrayList::new)); + + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(lazyList, location)) { + + } + + + try (EmbeddedStorageManager storageManager1 = EmbeddedStorage.start(lazyList, secondLocation)) { + + } + + } + + @Test + public void clear() + { + String value = "Hi, i am a great value test sentence"; + LazyHashMap map = Util.generateMap(100); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + } + + + map.clear(); + + assertTrue(map.isEmpty()); + + try (EmbeddedStorageManager storageManager = Util.startStorage(location)) { + storageManager.setRoot(map); + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = Util.startStorage(location)) { + LazyHashMap root = (LazyHashMap) storageManager.root(); + + assertTrue(root.isEmpty()); + } + + } + + + /** + * After fix diese Issue, add some other tests: terator.remove, Set.remove, removeAll, retainAll, and clear operations + */ + @Test + public void keySetRemove() + { + + LazyHashMap map = Util.generateMap(100); + + map.put(101, "some text"); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + map1.keySet() + .remove(101); + + } + + } + + @Test + public void keySet() + { + + LazyHashMap map = Util.generateMap(100); + + + Set keySet = map.keySet(); + assertEquals(100, keySet.size()); + map.put(101, "some text"); + assertEquals(101, keySet.size()); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + assertEquals(101, keySet.size()); + assertNotNull(map.get(101)); + } + + LazyHashMap map1 = new LazyHashMap<>(); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + map1.put(102, "another text"); + Set map1KeySet = map1.keySet(); + assertEquals(101, keySet.size()); + assertEquals(102, map1KeySet.size()); + } + + } + + @Test + public void values_clear() + { + LazyHashMap map = Util.generateMap(100); + LazyCollection values = map.values(); + values.clear(); + assertTrue(map.isEmpty()); + + map = Util.generateMap(100); + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + + } + LazyHashMap map1 = new LazyHashMap<>(); + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + LazyCollection values1 = map1.values(); + values1.clear(); + assertTrue(map1.isEmpty()); + + } + + } + + @Test + public void values_removeAll() + { + LazyHashMap map = Util.generateMap(100); + LazyCollection values = map.values(); + values.removeAll(map.values()); + assertTrue(map.isEmpty()); + + map = Util.generateMap(100); + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + + } + + LazyHashMap map1 = new LazyHashMap<>(); + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + LazyCollection values1 = map1.values(); + values1.removeAll(map.values()); + assertTrue(map1.isEmpty()); + + } + } + + @Test + public void entrySet() + { + LazyHashMap map = Util.generateMap(100); + LazySet> entries = map.entrySet(); + assertEquals(100, entries.size()); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + + } + LazyHashMap map1 = new LazyHashMap<>(); + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + LazySet> entries1 = map1.entrySet(); + assertEquals(100, entries1.size()); + + } + } + + @Test + void entryTest() + { + String value = "Ahoj"; + + LazyHashMap map = Util.generateMap(100); + LazySet> entries = map.entrySet(); + for (Map.Entry entry : entries) { + assertThrows(UnsupportedOperationException.class, () -> entry.setValue(value)); + } + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + + } + LazyHashMap map1 = new LazyHashMap<>(); + try (EmbeddedStorageManager storageManager = Util.startStorage(map1, location)) { + LazySet> entries1 = map1.entrySet(); + for (Map.Entry integerStringEntry : entries1) { + assertTrue(integerStringEntry.getValue() + .length() > 0); + assertTrue(integerStringEntry.getKey() >= 0); + } + } + } + + @Test + void replaceAll() + { + LazyHashMap map = Util.generateMap(100); + + assertThrows(UnsupportedOperationException.class, () -> map.replaceAll((key, oldValue) -> { + return oldValue + oldValue; + })); + } + + @Test + void putIfAbsent() + { + String s = "javax.net.ssl.keyStore"; + + LazyHashMap map = Util.generateMap(100); + String value = map.get(50); + map.put(50, null); + map.putIfAbsent(50, s); + + assertEquals(s, map.get(50)); + + } + + @Test + void remove_for_specific_key_and_value() + { + LazyHashMap map = Util.generateMap(100); + String value = map.get(60); + assertFalse(map.remove(60, "javax.net.ssl.keyStore")); + assertTrue(map.remove(60, value)); + assertEquals(99, map.size()); + } + + + @Test + void computeIfAbsent() + { + final String value = "ahoj"; + LazyHashMap map = Util.generateMap(100); + map.put(101, null); + + map.computeIfAbsent(101, v -> value); + + assertEquals(value, map.get(101)); + } + + @Test + void computeIfPresent() + { + final String value = "ahoj"; + LazyHashMap map = Util.generateMap(100); + map.put(101, value); + map.computeIfPresent(101, (k, v) -> v + value); + assertEquals(value + value, map.get(101)); + } + + @Test + void merge() + { + final String value = "ahoj"; + LazyHashMap map = Util.generateMap(100); + map.put(101, value); + map.compute(101, (k, v) -> v + value); + assertEquals(value + value, map.get(101)); + + map.merge(101, value, (k, v) -> v + v); + assertEquals(value + value, map.get(101)); + + } + + @Test + void ofTestEmpty() + { + LazyHashMap map = Util.generateMap(100); + Map immutableMap = Map.of(); + map.putAll(immutableMap); + assertEquals(100, map.size()); + } + + @Test + void ofTestWithValue() + { + LazyHashMap map = Util.generateMap(100); + Map immutableMap = Map.of(101, "PP", 102, "QQ", 103, "RR"); + map.putAll(immutableMap); + assertEquals(103, map.size()); + } + + @Test + void ofEntries() + { + LazyHashMap map = Util.generateMap(100); + Map immutableMap = Map.ofEntries(Map.entry(101, "ahoj"), + Map.entry(102, "ahoj2"), Map.entry(103, "ahoj3")); + map.putAll(immutableMap); + assertEquals(103, map.size()); + } + + @Test + void entry() + { + LazyHashMap map = Util.generateMap(100); + Map.Entry ahoj = Map.entry(101, "ahoj"); + assertThrows(UnsupportedOperationException.class, () -> map.entrySet() + .add(ahoj)); + + } + + @Test + void copyOf() + { + LazyHashMap map = Util.generateMap(100); + Map integerStringMap = Map.copyOf(map); + assertEquals(100, map.size()); + } + + @Test + void equals() + { + + LazyHashMap mapA = new LazyHashMap<>(); + mapA.put(1, "A"); + mapA.put(2, "B"); + mapA.put(2, "A"); + mapA.put(3, null); + mapA.put(null, "C"); + mapA.put(null, null); + + + LazyHashMap mapB = new LazyHashMap<>(); + mapB.put(1, "A"); + mapB.put(2, "B"); + mapB.put(2, "A"); + mapB.put(3, null); + mapB.put(null, "C"); + mapB.put(null, null); + + assertIterableEquals(mapA.entrySet(), mapB.entrySet()); + + + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapVariousTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapVariousTest.java new file mode 100644 index 000000000..fc16fc7bd --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/HashMapVariousTest.java @@ -0,0 +1,102 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.types.PrimitiveTypes; + +public class HashMapVariousTest +{ + + @TempDir + Path location; + + @Test + void isSegmentModifiedTest() + { + LazyHashMap map = new LazyHashMap<>(); + map.put(1, "ahoj"); + + try (EmbeddedStorageManager storageManager = Util.startStorage(map, location)) { + + map.segments() + .forEach(s -> { + assertTrue(s.isLoaded()); + assertFalse(s.isModified()); + }); + + map.put(1, "super"); + + map.segments() + .forEach(s -> { + assertTrue(s.isLoaded()); + assertTrue(s.isModified()); + }); + + } + } + + @Test + void isSegmentLoadedTest() + { + int count = 1; + LazyHashMap map = generateLazyHashMap(count); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, location)) { + + map.segments() + .forEach(s -> { + assertTrue(s.isLoaded()); + assertFalse(s.isModified()); + }); + + map.segments() + .forEach(LazyHashMap.Segment::unloadSegment); + map.segments() + .forEach(s -> + assertFalse(s.isLoaded())); + + map.put(1, null); + + map.segments() + .forEach(s -> { + assertTrue(s.isLoaded()); + }); + + } + } + + private static LazyHashMap generateLazyHashMap(int count) + { + LazyHashMap map = new LazyHashMap<>(); + for (int i = 0; i < count; i++) { + PrimitiveTypes type = new PrimitiveTypes(); + type.fillSampleData(); + map.put(i, type); + } + + return map; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapCloneTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapCloneTest.java new file mode 100644 index 000000000..c8aed49ab --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapCloneTest.java @@ -0,0 +1,38 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.junit.jupiter.api.Test; + +public class LazyHashMapCloneTest +{ + + @Test + void cloneMap() throws CloneNotSupportedException + { + final LazyHashMap map = new LazyHashMap<>(17); + + for (int i = 0; i < 100; i++) { + map.put("key " + i, "Value " + i); + } + + final LazyHashMap clonedMap = new LazyHashMap<>(map); + + assertNotEquals(clonedMap, map); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapNullTests.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapNullTests.java new file mode 100644 index 000000000..ba6b80eb1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapNullTests.java @@ -0,0 +1,222 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.junit.jupiter.api.Test; + +public class LazyHashMapNullTests +{ + + @Test + void addNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + final String refResult = refMap.put(null, "value of null key"); + final String lazyResult = lazyMap.put(null, "value of null key"); + + assertEquals(refResult, lazyResult); + } + + @Test + void addNullKeyTwice() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put(null, "value of null key"); + lazyMap.put(null, "value of null key"); + + final String refResult = refMap.put(null, "added again null key"); + final String lazyResult = lazyMap.put(null, "added again null key"); + + assertEquals(refResult, lazyResult); + } + + @Test + void containsNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put(null, "value of null key"); + lazyMap.put(null, "value of null key"); + + final boolean refResult = refMap.containsKey(null); + final boolean lazyResult = lazyMap.containsKey(null); + + assertEquals(refResult, lazyResult); + } + + @Test + void containsNoNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put("key a", "value of null key"); + lazyMap.put("key a", "value of null key"); + + final boolean refResult = refMap.containsKey(null); + final boolean lazyResult = lazyMap.containsKey(null); + + assertEquals(refResult, lazyResult); + } + + @Test + void getNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put(null, "value of null key"); + lazyMap.put(null, "value of null key"); + + final String refResult = refMap.get(null); + final String lazyResult = lazyMap.get(null); + + assertEquals(refResult, lazyResult); + } + + @Test + void getNoNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put("key a", "value of null key"); + lazyMap.put("key a", "value of null key"); + + final String refResult = refMap.get(null); + final String lazyResult = lazyMap.get(null); + + assertEquals(refResult, lazyResult); + } + + @Test + void removeNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put(null, "value of null key"); + lazyMap.put(null, "value of null key"); + + final String refResult = refMap.remove(null); + final String lazyResult = lazyMap.remove(null); + + assertEquals(refResult, lazyResult); + } + + @Test + void removeNoNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put("key a", "value of null key"); + lazyMap.put("key a", "value of null key"); + + final String refResult = refMap.remove(null); + final String lazyResult = lazyMap.remove(null); + + assertEquals(refResult, lazyResult); + } + + + @Test + void addNullValue() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + final String refResult = refMap.put("key a", null); + final String lazyResult = lazyMap.put("key a", null); + + assertEquals(refResult, lazyResult); + } + + @Test + void addNullValueTwice() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put("key a", null); + lazyMap.put("key a", null); + + final String refResult = refMap.put("key a", null); + final String lazyResult = lazyMap.put("key a", null); + + assertEquals(refResult, lazyResult); + } + + @Test + void getNullValue() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put("key a", null); + lazyMap.put("key a", null); + + final String refResult = refMap.get(null); + final String lazyResult = lazyMap.get(null); + + assertEquals(refResult, lazyResult); + } + + @Test + void removeNullValue() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put("key a", null); + lazyMap.put("key a", null); + + final boolean refResult = refMap.remove("key a", null); + final boolean lazyResult = lazyMap.remove("key a", null); + + assertEquals(refResult, lazyResult); + } + + @Test + void ReplaceNullKey() + { + final Map lazyMap = new LazyHashMap<>(); + final Map refMap = new HashMap<>(); + + refMap.put(null, "value of null key"); + lazyMap.put(null, "value of null key"); + + final String refResultReplace = refMap.replace(null, "replaced"); + final String lazyResultReplace = lazyMap.replace(null, "replaced"); + + assertEquals(refResultReplace, lazyResultReplace); + + final String refResult = refMap.get(null); + final String lazyResult = lazyMap.get(null); + + assertEquals(refResult, lazyResult); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapPersistenceTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapPersistenceTest.java new file mode 100644 index 000000000..d64d6591a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapPersistenceTest.java @@ -0,0 +1,176 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyHashMapPersistenceTest +{ + + + @SuppressWarnings("unchecked") + @Test + void EmptyMap(@TempDir final Path path) + { + LazyHashMap map = new LazyHashMap<>(3); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + } + + LazyHashMap map2 = new LazyHashMap<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map2, path)) { + + assertNotNull(map2); + map2.remove(12); + map2.put(111, "Hello"); + + assertEquals("Hello", map2.get(111)); + } + + } + + @Test + void testToString(@TempDir final Path path) + { + LazyHashMap map = new LazyHashMap<>(3); + for (int i = 0; i < 10; i++) { + map.put(i, "E" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + + map.values() + .forEach(String::isBlank); + + assertEquals("{[ 1 unloaded Elements], [ 1 unloaded Elements], [ 1 unloaded Elements], [3=E3], [4=E4], [5=E5], [6=E6], [7=E7, 8=E8, 9=E9]}", + map.toString()); + + } + } + + @Test + void testToStringEmpty(@TempDir final Path path) + { + LazyHashMap map = new LazyHashMap<>(3); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + final AtomicInteger counter = new AtomicInteger(); + map.segments() + .forEach(s -> { + if (counter.getAndIncrement() % 2 > 0) { + s.unloadSegment(); + } + }); + + assertEquals("{}", map.toString()); + + } + } + + @Test + void isSegmentModifiedTest(@TempDir final Path path) + { + LazyHashMap map = new LazyHashMap<>(); + map.put(1, "ahoj"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + + map.segments() + .forEach(s -> { + assertTrue(s.isLoaded()); + assertFalse(s.isModified()); + }); + + map.put(1, "super"); + + map.segments() + .forEach(s -> { + assertTrue(s.isLoaded()); + assertTrue(s.isModified()); + }); + + storageManager.store(map); + } + + LazyHashMap reloadedMap = new LazyHashMap<>(); + try (final EmbeddedStorageManager reloadedStorage = EmbeddedStorage.start(reloadedMap, path)) { + assertEquals(map.get(1), reloadedMap.get(1)); + } + } + + @Test + void replaceTest(@TempDir final Path path) + { + LazyHashMap map = new LazyHashMap<>(13); + map.put("Key 1", new MapEntry("to be replaced")); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + map.replace("Key 1", new MapEntry("has been replaced")); + storage.store(map); + } + + LazyHashMap reloadedMap = new LazyHashMap<>(); + try (final EmbeddedStorageManager reloadedStorage = EmbeddedStorage.start(reloadedMap, path)) { + final MapEntry reloadedEntry = reloadedMap.get("Key 1"); + assertEquals("has been replaced", reloadedEntry.name); + } + + } + + static class MapEntry + { + String name; + + public MapEntry(final String name) + { + super(); + this.name = name; + } + + @Override + public String toString() + { + return "id: " + this.name; + } + + @Override + public int hashCode() + { + return Objects.hash(this.name); + } + + @Override + public boolean equals(final Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (this.getClass() != obj.getClass()) + return false; + final MapEntry other = (MapEntry) obj; + return Objects.equals(this.name, other.name); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapRandomPersistenceTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapRandomPersistenceTest.java new file mode 100644 index 000000000..608c682a3 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapRandomPersistenceTest.java @@ -0,0 +1,57 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.Random; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyHashMapRandomPersistenceTest extends LazyHashMapRandomTest +{ + + private final int cycles = 100; + + @SuppressWarnings("unchecked") + @Test + @Disabled("This test takes to long to run.") + void randomPersistenceTest(@TempDir final Path path) + { + + this.rnd = new Random(System.nanoTime()); + + this.lazyMap = new LazyHashMap<>(13); + try (EmbeddedStorageManager storage = EmbeddedStorage.start(lazyMap, path)) { + + } + + for (int i = 0; i < this.cycles; i++) { + try (EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + this.lazyMap = (LazyHashMap) storage.root(); + this.compare(); + this.randomAction(); + storage.store(this.lazyMap); + } + + this.lazyMap = null; + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapRandomTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapRandomTest.java new file mode 100644 index 000000000..bed3e4b86 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapRandomTest.java @@ -0,0 +1,245 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.*; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.util.logging.Logging; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +// 271124223868563 + +public class LazyHashMapRandomTest +{ + + private final static Logger logger = Logging.getLogger(LazyHashMapRandomTest.class); + + public Random rnd = new Random(42); + private final int numAction = 7; + private final int cycles = 10000; + + LazyHashMap lazyMap = new LazyHashMap<>(10); + Map refMap = new HashMap<>(); + + List protocoll = new ArrayList<>(); + + public static void main(final String[] args) + { + + LazyHashMap l = new LazyHashMap<>(10); + + l.put("Key_" + 1402217696, "Value " + 1402217696); + + final Iterator lazyIter = l.keySet().iterator(); + while (lazyIter.hasNext()) { + final String key = lazyIter.next(); + if (key.contains("21")) { + lazyIter.remove(); + } + } + + final Iterator lazyIter2 = l.keySet().iterator(); + while (lazyIter2.hasNext()) { + final String key = lazyIter2.next(); + if (key.contains("21")) { + lazyIter2.remove(); + } + } + + //l.replace(null, null); + + l.remove("Key_" + 1081360197); + } + + + @BeforeEach + void setupTest() + { + this.rnd = new Random(System.nanoTime()); + this.protocoll = new ArrayList<>(); + + this.lazyMap = new LazyHashMap<>(10); + this.refMap = new HashMap<>(); + } + + + @RepeatedTest(value = 1) + @Disabled("This test takes to long to run.") + void repeatedRandomActionTest() + { + this.RandomActionTest(); + } + + + @Test + @Disabled("This test takes to long to run.") + void RandomActionTest() + { + for (int i = 0; i < this.cycles; i++) { + this.randomAction(); + } + + this.compare(); + + while (!this.lazyMap.isEmpty()) { + final String key = this.getRndKey(); + this.refMap.remove(key); + this.lazyMap.remove(key); + } + + assertEquals(this.refMap.size(), this.lazyMap.size(), "size should be 0"); + } + + public void compare() + { + assertEquals(this.refMap.size(), this.lazyMap.size()); + + for (final String key : this.refMap.keySet()) { + final String ref = this.refMap.get(key); + final String lazy = this.lazyMap.get(key); + assertEquals(ref, lazy, "Values for key " + key + " don't match!"); + } + } + + public void randomAction() + { + switch (this.rnd.nextInt(this.numAction)) { + case 0: + this.protocoll.add("actionPut"); + this.actionPut(); + break; + case 1: + this.protocoll.add("actionRemove"); + this.actionRemove(); + break; + case 2: + this.protocoll.add("actionReplace"); + this.actionReplace(); + break; + case 3: + this.protocoll.add("actionReplace"); + this.actionReplaceEquals(); + break; + case 4: + this.protocoll.add("actionKeyIterateRemove"); + this.actionKeyIterateRemove(); + break; + case 5: + this.protocoll.add("actionPut"); + this.actionPut(); + break; + case 6: + this.protocoll.add("actionPut"); + this.actionPut(); + break; + default: + break; + } + } + + public void actionKeyIterateRemove() + { + + logger.debug("actionKeyIterateRemove"); + + final Iterator refIter = this.refMap.keySet().iterator(); + while (refIter.hasNext()) { + final String key = refIter.next(); + if (key.contains("21")) { + refIter.remove(); + } + } + + final Iterator lazyIter = this.lazyMap.keySet().iterator(); + while (lazyIter.hasNext()) { + final String key = lazyIter.next(); + if (key.contains("21")) { + lazyIter.remove(); + } + } + } + + public void actionReplace() + { + + if (this.refMap.keySet().size() <= 0) return; + + final String key = this.getRndKey(); + logger.debug("replace {} ", key); + + final String lazy = this.lazyMap.replace(key, "replaced " + key); + final String ref = this.refMap.replace(key, "replaced " + key); + + assertEquals(ref, lazy); + } + + public void actionReplaceEquals() + { + + if (this.refMap.keySet().size() <= 0) return; + + final String key = this.getRndKey(); + logger.debug("replace {} ", key); + + final String currentValue = this.refMap.get(key); + + final boolean lazy = this.lazyMap.replace(key, currentValue, "replaced " + key); + final boolean ref = this.refMap.replace(key, currentValue, "replaced " + key); + + assertEquals(ref, lazy); + } + + public String getRndKey() + { + final int r = this.rnd.nextInt(this.refMap.keySet().size()); + final Iterator iter = this.refMap.keySet().iterator(); + for (int i = 0; i < r - 1; i++) { + iter.next(); + } + final String key = iter.next(); + return key; + } + + public void actionRemove() + { + final int id = this.rnd.nextInt(); + + logger.debug("remove {} ", id); + + final String lazy = this.lazyMap.remove("Key_" + id); + final String ref = this.refMap.remove("Key_" + id); + assertEquals(ref, lazy); + } + + public void actionPut() + { + final int id = this.rnd.nextInt(); + + logger.debug("put {} ", id); + + final String lazy = this.lazyMap.put("Key_" + id, "Value " + id); + final String ref = this.refMap.put("Key_" + id, "Value " + id); + assertEquals(ref, lazy); + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapSpliteratorTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapSpliteratorTest.java new file mode 100644 index 000000000..9f0d5ca06 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapSpliteratorTest.java @@ -0,0 +1,251 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Spliterator; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.junit.jupiter.api.Test; + +public class LazyHashMapSpliteratorTest +{ + + @Test + void StreamParallelKeySet() + { + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final long count = map.keySet() + .parallelStream() + .filter(e -> e.contains("9")) + .count(); + assertEquals(19, count); + } + + @Test + void StreamParallelValuesSet() + { + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final long count = map.values() + .parallelStream() + .filter(e -> e.contains("9")) + .count(); + assertEquals(19, count); + } + + @Test + void StreamParallelEntrySet() + { + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final long count = map.entrySet() + .parallelStream() + .filter(e -> e.getKey().contains("9")) + .count(); + assertEquals(19, count); + } + + @Test + void StreamKeySet() + { + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final long count = map.keySet() + .stream() + .filter(e -> e.contains("9")) + .count(); + assertEquals(19, count); + } + + @Test + void StreamValueSet() + { + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final long count = map.values() + .stream() + .filter(e -> e.contains("9")) + .count(); + assertEquals(19, count); + } + + @Test + void StreamEntrySet() + { + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final long count = map.entrySet() + .stream() + .filter(e -> e.getKey().contains("9")) + .count(); + assertEquals(19, count); + } + + @Test + void createSpliteratorKeySetRecursive() + { + + final int numElements = 100; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final List> spliterators = this.splitAll(map.keySet().spliterator()); + + for (final Spliterator spliterator : spliterators) { + while (spliterator.tryAdvance(e -> e.toString())) { + //noop + } + } + } + + @Test + void createSpliteratorValueSetRecursive() + { + + final int numElements = 2000; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final List> spliterators = this.splitAll(map.values().spliterator()); + + assertTrue(spliterators.size() > 1); + } + + @Test + void createSpliteratorEntrySetRecursive() + { + + final int numElements = 2000; + + final LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Entry " + i); + } + + final List> spliterators = this.splitAll(map.entrySet().spliterator()); + + assertTrue(spliterators.size() > 1); + } + + @Test + void StreamEmptyKeySet() + { + final LazyHashMap map = new LazyHashMap<>(); + + final long count = map.keySet() + .stream() + .filter(e -> e.contains("9")) + .count(); + assertEquals(0, count); + } + + @Test + void StreamEmptyValueSet() + { + final LazyHashMap map = new LazyHashMap<>(); + + final long count = map.values() + .stream() + .filter(e -> e.contains("9")) + .count(); + assertEquals(0, count); + } + + @Test + void StreamEmptyEntrySet() + { + final LazyHashMap map = new LazyHashMap<>(); + + final long count = map.entrySet() + .stream() + .filter(e -> e.getKey().contains("9")) + .count(); + assertEquals(0, count); + } + + private List> splitAll(final Spliterator spliterator) + { + final List> spliterators = new ArrayList<>(); + final Spliterator split = spliterator.trySplit(); + + if (split != null) { + spliterators.addAll(this.splitAll(spliterator)); + spliterators.addAll(this.splitAll(split)); + } else { + spliterators.add(spliterator); + } + + return spliterators; + } + + // java > 16 test +// @Test +// void emptySpliteratorTest() +// { +// LazyHashMap stringStringLazyHashMap = new LazyHashMap<>(); +// stringStringLazyHashMap.values().stream().toList(); +// } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapStreamsTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapStreamsTest.java new file mode 100644 index 000000000..20adf5d55 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapStreamsTest.java @@ -0,0 +1,198 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.nio.file.Path; +import java.util.Map.Entry; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyCollection; +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyHashMapStreamsTest +{ + + @Test + void readValueStreamAndCloseTest(@TempDir final Path path) + { + final LazyHashMap map = createMap(6, 100); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + + final LazyCollection values = map.values(); + values.iterateLazyReferences(l -> l.clear()); + values.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long count; + try (final Stream stream = values.stream()) { + count = stream + .filter(e -> e.contains("Value")) + //.peek( e -> System.out.println(e)) + .count(); + } + + assertEquals(100, count); + + TestHelpers.assertLoadedSegment(map); + } + } + + @Test + void readValueParallelStreamAndCloseTest(@TempDir final Path path) + { + final LazyHashMap map = createMap(6, 100); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + + final LazyCollection values = map.values(); + values.iterateLazyReferences(l -> l.clear()); + values.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long count; + try (final Stream stream = values.parallelStream()) { + count = stream + .filter(e -> e.contains("Value")) + .count(); + } + + assertEquals(100, count); + + TestHelpers.assertLoadedSegment(map); + + } + } + + @Test + void readKeyStreamAndCloseTest(@TempDir final Path path) + { + final LazyHashMap map = createMap(6, 100); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + + final LazyCollection keys = map.keySet(); + keys.iterateLazyReferences(l -> l.clear()); + keys.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long count; + try (final Stream stream = keys.stream()) { + count = stream + .filter(e -> e.contains("Key")) + //.peek( e -> System.out.println(e)) + .count(); + } + + assertEquals(100, count); + TestHelpers.assertLoadedSegment(map); + } + } + + @Test + void readKeyParallelStreamAndCloseTest(@TempDir final Path path) + { + final LazyHashMap map = createMap(6, 100); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + final LazyCollection keys = map.keySet(); + keys.iterateLazyReferences(l -> l.clear()); + keys.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long count; + try (final Stream stream = keys.parallelStream()) { + count = stream + .filter(e -> e.contains("Key")) + //.peek( e -> System.out.println(e)) + .count(); + } + + assertEquals(100, count); + TestHelpers.assertLoadedSegment(map); + } + } + + @Test + void readEntryStreamAndCloseTest(@TempDir final Path path) + { + final LazyHashMap map = createMap(6, 100); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + + final LazyCollection> entries = map.entrySet(); + entries.iterateLazyReferences(l -> l.clear()); + entries.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long count; + try (final Stream> stream = entries.stream()) { + count = stream + .filter(e -> e.getKey() + .contains("Key")) + .filter(e -> e.getValue() + .contains("Value")) + .count(); + } + + assertEquals(100, count); + TestHelpers.assertLoadedSegment(map); + } + } + + @Test + void readEntryParallelStreamAndCloseTest(@TempDir final Path path) + { + final LazyHashMap map = createMap(6, 100); + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + + storage.setRoot(map); + storage.storeRoot(); + + final LazyCollection> entries = map.entrySet(); + entries.iterateLazyReferences(l -> l.clear()); + entries.iterateLazyReferences(l -> assertFalse(l.isLoaded(), "list segment loaded!")); + + long count; + try (final Stream> stream = entries.parallelStream()) { + count = stream + .filter(e -> e.getKey() + .contains("Key")) + .filter(e -> e.getValue() + .contains("Value")) + //.peek( e -> System.out.println(e)) + .count(); + } + + assertEquals(100, count); + TestHelpers.assertLoadedSegment(map); + } + } + + private LazyHashMap createMap(int maxSegmentSize, int count) + { + final LazyHashMap map = new LazyHashMap<>(maxSegmentSize); + + for (int i = 0; i < count; i++) { + map.put("Key " + i, "Value " + i); + } + return map; + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapTest.java new file mode 100644 index 000000000..dd4e9853d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapTest.java @@ -0,0 +1,506 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.junit.jupiter.api.Test; + +public class LazyHashMapTest +{ + + @Test + void addNullObject() + { + LazyHashMap map = new LazyHashMap<>(); + + map.put("Key 1", null); + final String value = map.get("Key 1"); + + assertNull(value, "expected null value"); + assertTrue(map.containsKey("Key 1"), "key not found in map"); + } + + @Test + void addSingleValueToEmptyMap() + { + LazyHashMap map = new LazyHashMap<>(); + + map.put("Key 1", "Value 1"); + final String value = map.get("Key 1"); + + assertEquals("Value 1", value); + } + + + @Test + void addManyIntKey() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put(i, "Value " + i); + } + + for (int i = 0; i < 100; i++) { + final String value = map.get(i); + assertEquals("Value " + i, value, "Value " + i + " does not match!"); + } + } + + @Test + void addManyStringKey() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + for (int i = 0; i < 100; i++) { + final String value = map.get("Key " + i); + assertEquals("Value " + i, value, "Value " + i + " does not match!"); + } + + } + + @Test + void removeFromFront() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + for (int i = 0; i < 100; i++) { + final String value = map.remove("Key " + i); + assertEquals("Value " + i, value, "Value " + i + " does not match!"); + } + assertTrue(map.isEmpty()); + } + + @Test + void removeFromBack() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + for (int i = 99; i >= 0; i--) { + final String value = map.remove("Key " + i); + assertEquals("Value " + i, value, "Value " + i + " does not match!"); + } + assertTrue(map.isEmpty()); + } + + @Test + void removeRandom() + { + LazyHashMap map = new LazyHashMap<>(); + + final int numElements = 100; + final List toBeRemoved = new ArrayList<>(numElements); + + for (int i = 0; i < numElements; i++) { + map.put("Key " + i, "Value " + i); + toBeRemoved.add(i); + } + + final Random rnd = new Random(System.nanoTime()); + + while (!map.isEmpty()) { + final int rint = toBeRemoved.remove(rnd.nextInt(toBeRemoved.size())); + final String removed = map.remove("Key " + rint); + + assertEquals("Value " + rint, removed, "removed Value " + rint + " does not match!"); + } + assertTrue(map.isEmpty()); + + final AtomicInteger counter = new AtomicInteger(); + map.segments() + .forEach(s -> counter.incrementAndGet()); + assertEquals(0, counter.get()); + } + + @Test + void removeKeyValue() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + assertFalse(map.remove("Key 11", "Value 12"), "removing non existing value should return false"); + assertTrue(map.remove("Key 11", "Value 11"), "removing existing value should return true"); + assertEquals(99, map.size()); + assertFalse(map.containsValue("Value 11")); + } + + + @Test + void replace() + { + LazyHashMap map = new LazyHashMap<>(); + final String putResult = map.put("Key", "Value"); + assertEquals(null, putResult); + + final String replaceResult = map.replace("Key", "Replaced"); + assertEquals("Value", replaceResult); + assertEquals("Replaced", map.get("Key")); + + final String notReplacedResult = map.replace("no key", "not in map"); + assertNull(notReplacedResult); + assertNull(map.get("no key")); + } + + @Test + void replaceIfEqual() + { + LazyHashMap map = new LazyHashMap<>(); + final String putResult = map.put("Key", "Value"); + assertEquals(null, putResult); + + final boolean replaceResult = map.replace("Key", "Value", "Replaced"); + assertTrue(replaceResult); + assertEquals("Replaced", map.get("Key")); + + final boolean replaceResult_noValue = map.replace("Key", "Value", "Replaced again"); + assertFalse(replaceResult_noValue); + assertEquals("Replaced", map.get("Key")); + + final boolean replaceResult_noKey = map.replace("no key", "Replaced", "not in map"); + assertFalse(replaceResult_noKey); + assertNull(map.get("no key")); + } + + @Test + void forEach() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + map.forEach((k, v) -> { + assertNotNull(k, "key should not be null!"); + assertNotNull(v, "value should not be null"); + }); + } + + @Test + void entrySet() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + final Set> entrySet = map.entrySet(); + assertNotNull(entrySet); + assertEquals(100, entrySet.size()); + } + + @Test + void entrySetRemove() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + final Set> entrySet = map.entrySet(); + assertNotNull(entrySet); + assertEquals(100, entrySet.size()); + + final boolean removed = entrySet.remove(Map.entry("Key 11", "Value 11")); + assertTrue(removed, "entrySet remove expected to return true"); + } + + + @Test + void iteratorKeySet() + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + int total = 0; + int count = 0; + final Iterator iter = map.keySet() + .iterator(); + while (iter.hasNext()) { + final String v = iter.next(); + //System.out.println(v); + if (v.contains("0")) { + count++; + } + total++; + } + + assertEquals(10, count); + assertEquals(100, total); + + } + + @Test + void iteratorKeySetRemove() + { + + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + final Iterator iter = map.keySet() + .iterator(); + while (iter.hasNext()) { + final String v = iter.next(); + //System.out.println(v); + if (v.contains("0")) { + iter.remove(); + } + } + + assertEquals(90, map.size()); + + map.values() + .forEach(v -> { + assertFalse(v.contains("0"), "Element should be removed!"); + }); + + } + + @Test + void iteratorKeySetRemoveAll() + { + + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < 100; i++) { + map.put("Key " + i, "Value " + i); + } + + final Iterator iter = map.keySet() + .iterator(); + while (iter.hasNext()) { + final String v = iter.next(); + //System.out.println(v); + iter.remove(); + } + + assertEquals(0, map.size()); + + map.values() + .forEach(v -> { + assertFalse(v.contains("0"), "Element should be removed!"); + }); + + final AtomicInteger counter = new AtomicInteger(); + map.segments() + .forEach(s -> counter.incrementAndGet()); + assertEquals(0, counter.get()); + } + + @Test + void removeNull() + { + LazyHashMap map = new LazyHashMap<>(); + + map.put("Entry 0", null); + assertEquals(1, map.size()); + + map.remove("Entry 0"); + assertEquals(0, map.size()); + } + + @Test + void replaceNull() + { + LazyHashMap map = new LazyHashMap<>(); + map.put("Entry 0", null); + assertNull(map.replace("Entry 0", "Hello World")); + assertEquals("Hello World", map.get("Entry 0")); + } + + @Test + void replaceWithNull() + { + LazyHashMap map = new LazyHashMap<>(); + map.put("Entry 0", "Hello World"); + assertEquals("Hello World", map.replace("Entry 0", null)); + assertEquals(null, map.get("Entry 0")); + } + + @Test + public void containsKeyNull() + { + LazyHashMap map = new LazyHashMap<>(); + map.put(105, null); + map.put(5, null); + + assertTrue(map.containsValue(null)); + assertTrue(map.containsKey(5)); + assertFalse(map.containsValue("not contained")); + assertFalse(map.containsKey(11)); + } + + @Test + public void getValueNull() + { + LazyHashMap map = new LazyHashMap<>(); + map.put(105, null); + map.put(5, null); + map.put(null, null); + + map.get(null); + } + + @Test + void copyTest() throws CloneNotSupportedException + { + LazyHashMap map = new LazyHashMap<>(); + for (int i = 0; i < 100; i++) { + map.put(i, "Value " + i); + } + LazyHashMap clone = new LazyHashMap<>(map); + + assertIterableEquals(map.entrySet(), clone.entrySet()); + assertEquals(map.size(), clone.size(), "map size"); + assertEquals(map.getMaxSegmentSize(), clone.getMaxSegmentSize(), "maxSegmentSize"); + + //test for non cloned segments + final List originalSegments = new ArrayList<>(); + map.segments() + .forEach(s -> originalSegments.add(s)); + final List clonedSegments = new ArrayList<>(); + clone.segments() + .forEach(s -> clonedSegments.add(s)); + + originalSegments.forEach(o -> assertFalse(clonedSegments.contains(o), "same segment in original and clone")); + + final List originalEntries = Arrays.asList(map.entrySet() + .toArray()); + final List clonedEntries = Arrays.asList(clone.entrySet() + .toArray()); + assertEquals(100, clonedEntries.size()); + assertIterableEquals(originalEntries, clonedEntries); + + for (int i = 0; i < originalEntries.size(); i++) { + final Object originalEntry = originalEntries.get(i); + final Object clonedEntry = clonedEntries.get(i); + assertEquals(originalEntry, clonedEntry, "originalEntry not equals clonedEntry"); + assertFalse(originalEntry == clonedEntry, "originalEntry == clonedEntry"); + } + + } + + @Test + void clearTest() + { + LazyHashMap map = new LazyHashMap<>(); + for (int i = 0; i < 100; i++) { + map.put(i, "Value " + i); + } + assertEquals(100, map.size()); + + map.clear(); + assertEquals(0, map.size()); + + final AtomicInteger counter = new AtomicInteger(); + map.segments() + .forEach(s -> counter.incrementAndGet()); + assertEquals(0, counter.get()); + } + + @Test + void addAfterClear() + { + LazyHashMap map = new LazyHashMap<>(); + for (int i = 0; i < 100; i++) { + map.put(i, "Value " + i); + } + assertEquals(100, map.size()); + + map.clear(); + assertEquals(0, map.size()); + + map.put(1, "Value 1"); + assertEquals(1, map.size()); + + assertEquals("Value 1", map.get(1)); + } + + @Test + void getFromEmptyMap() + { + LazyHashMap map = new LazyHashMap<>(); + assertNull(map.get(1)); + } + + @Test + void containsKeyEmptyMap() + { + LazyHashMap map = new LazyHashMap<>(); + assertFalse(map.containsKey(1)); + } + + @Test + void removeFromEmptyMap() + { + LazyHashMap map = new LazyHashMap<>(); + assertNull(map.remove(1)); + } + + @Test + void replaceFromEmptyMap() + { + LazyHashMap map = new LazyHashMap<>(); + assertNull(map.replace(1, "non")); + } + + @Test + void replaceIfEqualsFromEmptyMap() + { + LazyHashMap map = new LazyHashMap<>(); + assertFalse(map.replace(1, "non", "new")); + } + + @Test + void ReplaceAll() + { + LazyHashMap map = new LazyHashMap<>(); + map.put(1, "Entry 1"); + assertThrows(UnsupportedOperationException.class, () -> + map.replaceAll((k, v) -> { + return v.toUpperCase(); + })); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapUnloadingTests.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapUnloadingTests.java new file mode 100644 index 000000000..10de66dd0 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/LazyHashMapUnloadingTests.java @@ -0,0 +1,250 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Isolated; + +@Isolated +public class LazyHashMapUnloadingTests +{ + + @Test + void autoUnloadUnloadRemovedSegments(@TempDir final Path path) throws InterruptedException + { + + LazyHashMap map = new LazyHashMap<>(3, new LazySegmentUnloader.Timed(50)); + for (int i = 0; i < 100; i++) { + map.put("MyKey" + i, "E" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + + assertEquals(43, getLoadedSegments(map).loaded); + assertEquals(0, getLoadedSegments(map).unloaded); + + for (int i = 30; i < 80; i++) { + map.remove("MyKey" + i, "E" + i); + } + storageManager.store(map); + + Thread.sleep(100); + map.get("MyKey30"); + + assertEquals(1, getLoadedSegments(map).loaded); + assertEquals(22, getLoadedSegments(map).unloaded); + + } + + LazyHashMap map2 = new LazyHashMap<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map2, path)) { + map2.entrySet() + .iterateLazyReferences(l -> assertFalse(l.isLoaded())); + } + + } + + @Test + void unloadingWhilePut(@TempDir final Path path) + { + + LazyHashMap map = new LazyHashMap<>(10); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + + for (int i = 0; i < 100; i++) { + map.put("MyKey" + i, "E" + i); + storageManager.store(map); + // System.out.println(getLoadedSegements(map)); + } + Assertions.assertNotEquals(0, getLoadedSegments(map).unloaded); + } + } + + + @Test + void unloadSegment(@TempDir final Path path) + { + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + + LazyHashMap map = createLazyMap(44); + + storage.setRoot(map); + storage.storeRoot(); + + map.segments() + .forEach(LazyHashMap.Segment::unloadSegment); + + map.entrySet() + .iterateLazyReferences(l -> assertFalse(l.isLoaded())); + + } + } + + @Test + void unloadStored(@TempDir final Path path) + { + + LazyHashMap map = createLazyMap(44); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(map, path)) { + + map.entrySet() + .iterateLazyReferences(l -> l.clear()); + map.entrySet() + .iterateLazyReferences(l -> assertFalse(l.isLoaded())); + + } + } + + @Test + void UnloadNotStored(@TempDir final Path path) + { + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + + LazyHashMap map = createLazyMap(44); + storage.setRoot(map); + + map.entrySet() + .iterateLazyReferences(l -> l.clear()); + map.entrySet() + .iterateLazyReferences(l -> assertTrue(l.isLoaded())); + + } + } + + @Test + void unloaded_reloadedStorage(@TempDir final Path path) + { + + try (final EmbeddedStorageManager storageReloaded = createRealodedStorageWithList(path)) { + + @SuppressWarnings("unchecked") + LazyHashMap mapReloaded = (LazyHashMap) storageReloaded.root(); + + mapReloaded.entrySet() + .iterateLazyReferences(l -> assertFalse(l.isLoaded())); + mapReloaded.entrySet() + .iterateLazyReferences(l -> l.clear()); + + } + } + + @Test + void UnloadNotStored_reloadedStorage(@TempDir final Path path) + { + + try (final EmbeddedStorageManager storageReloaded = createRealodedStorageWithList(path)) { + + @SuppressWarnings("unchecked") + LazyHashMap mapReloaded = (LazyHashMap) storageReloaded.root(); + mapReloaded.entrySet() + .iterateLazyReferences(l -> assertFalse(l.isLoaded())); + + mapReloaded.put(44, "New Entry"); + + mapReloaded.entrySet() + .iterateLazyReferences(l -> l.clear()); + + mapReloaded.segments() + .forEach(s -> { + if (s.isModified()) { + assertTrue(s.isLoaded(), "expected modified segment to be loaded!"); + } else { + assertFalse(s.isLoaded(), "expectedunmodified segment to be unloaded!"); + } + }); + + storageReloaded.store(mapReloaded); + mapReloaded.entrySet() + .iterateLazyReferences(l -> l.clear()); + mapReloaded.entrySet() + .iterateLazyReferences(l -> assertFalse(l.isLoaded())); + + } + } + + + static EmbeddedStorageManager createRealodedStorageWithList(final Path path) + { + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(path)) { + LazyHashMap map = createLazyMap(44); + storage.setRoot(map); + storage.storeRoot(); + storage.shutdown(); + + System.gc(); + + } + + return EmbeddedStorage.start(path); + } + + private static LazyHashMap createLazyMap(final int size) + { + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < size; i++) { + map.put(i, "Entry " + i); + } + return map; + } + + private SegmentStatistics getLoadedSegments(LazyHashMap map) + { + final AtomicInteger loadedCount = new AtomicInteger(); + final AtomicInteger unloadedCount = new AtomicInteger(); + map.segments() + .forEach(s -> { + if (s.isLoaded()) { + loadedCount.incrementAndGet(); + } else { + unloadedCount.incrementAndGet(); + } + }); + + return new SegmentStatistics(loadedCount.get(), unloadedCount.get()); + } + + static class SegmentStatistics + { + int loaded; + int unloaded; + + public SegmentStatistics(final int loaded, final int unloaded) + { + super(); + this.loaded = loaded; + this.unloaded = unloaded; + } + + @Override + public String toString() + { + return "SegmentStatistics [loaded=" + this.loaded + ", unloaded=" + this.unloaded + "]"; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/TestHelpers.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/TestHelpers.java new file mode 100644 index 000000000..1e26b826a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/TestHelpers.java @@ -0,0 +1,67 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySegment; + +public class TestHelpers +{ + + static public void assertLoadedSegment(LazyHashMap map) + { + + int expected = 0; + + try { + final Field unloader = map.getClass() + .getDeclaredField("unloader"); + unloader.setAccessible(true); + final Field load = unloader.get(map) + .getClass() + .getDeclaredField("desiredLoadCount"); + load.setAccessible(true); + final Object ul = unloader.get(map); + expected = load.getInt(ul); + + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + assertTrue(getLoadedSegments(map).size() <= expected); + } + + static public List> getLoadedSegments(LazyHashMap map) + { + + final Iterable.Segment> segments = map.segments(); + + final List> loadedSegments = new ArrayList<>(); + segments.forEach(s -> { + if (s.isLoaded()) { + loadedSegments.add(s); + } + }); + + return loadedSegments; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/TwoStoragesTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/TwoStoragesTest.java new file mode 100644 index 000000000..4f8a0faf8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/TwoStoragesTest.java @@ -0,0 +1,142 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Path; +import java.util.HashMap; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class TwoStoragesTest +{ + + @TempDir + Path location; + + private static PrintStream originalErr; + + @BeforeAll + static void setupErr() + { + originalErr = System.err; + System.setErr(new PrintStream(new OutputStream() + { + @Override + public void write(int b) + { + // Discard output + } + })); + } + + @AfterAll + static void restoreErr() + { + System.setErr(originalErr); + } + + @Test + public void hashMapkeySetRemoveTest(@TempDir final Path path) + { + final HashMap map = new HashMap<>(); + + map.put(101, "some text"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + //no op + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + + map.keySet() + .remove(101); + } + } + + @Test + public void keySetRemoveTest(@TempDir final Path path) + { + LazyHashMap map = new LazyHashMap<>(); + + map.put(101, "some text"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + //no op + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, path)) { + + map.keySet() + .remove(101); + } + } + + @Test + public void saveSecondTimeTest(@TempDir final Path secondLocation) + { + LazyHashMap map = generateMap(11); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, this.location)) { + //map.segments().forEach(s -> s.unloadSegment()); + } + + + assertThrows(IllegalStateException.class, () -> + EmbeddedStorage.start(map, secondLocation)); + + + } + + public static class MyRoot + { + Lazy lazy; + + public MyRoot(final String content) + { + super(); + this.lazy = Lazy.Reference(content); + } + + } + + public static LazyHashMap generateMap(final Integer count) + { + return generateMap(count, 0); + } + + public static LazyHashMap generateMap(final Integer count, final int keyStart) + { + + LazyHashMap map = new LazyHashMap<>(); + + for (int i = 0; i < count; i++) { + map.put(i + keyStart, "Hello World " + i); + } + + return map; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/Util.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/Util.java new file mode 100644 index 000000000..cd394e96b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/Util.java @@ -0,0 +1,85 @@ +package test.eclipse.store.collections.lazy.hashmap; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.HashMap; + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; + +import net.datafaker.Faker; + +public class Util +{ + + static Faker faker = new Faker(); + + public static LazyHashMap generateMap(Integer count) + { + return generateMap(count, 0); + } + + public static LazyHashMap generateMap(Integer count, int keyStart) + { + + LazyHashMap map = new LazyHashMap<>(); + + + for (int i = 0; i < count; i++) { + map.put(i + keyStart, faker.lorem() + .sentence()); + } + + return map; + } + + public static HashMap generateHashMap(Integer count) + { + return generateHashMap(count, 0); + } + + public static HashMap generateHashMap(Integer count, int keyStart) + { + + HashMap map = new HashMap<>(); + + + for (int i = 0; i < count; i++) { + map.put(i + keyStart, faker.lorem() + .sentence()); + } + + return map; + } + + public static EmbeddedStorageManager startStorage(LazyHashMap root, final Path path) + { + final EmbeddedStorageManager storage = EmbeddedStorage + .Foundation(path) + .start(root); + return storage; + } + + public static EmbeddedStorageManager startStorage(final Path path) + { + final EmbeddedStorageManager storage = EmbeddedStorage + .Foundation(path) + .start(); + return storage; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/HashMapGenerator.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/HashMapGenerator.java new file mode 100644 index 000000000..38a7e4b08 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/HashMapGenerator.java @@ -0,0 +1,46 @@ +package test.eclipse.store.collections.lazy.hashmap.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; + +import net.datafaker.Faker; + +public class HashMapGenerator +{ + + static Faker faker = new Faker(); + + public static LazyHashMap generate(int count, LazySegmentUnloader unloader) + { + + return generate(count, unloader, 1000); + } + + public static LazyHashMap generate(int count, LazySegmentUnloader unloader, int segmentSize) + { + LazyHashMap persons = new LazyHashMap<>(segmentSize, unloader); + for (int i = 0; i < count; i++) { + MapPerson person = new MapPerson(); + person.setFirstname(faker.name().firstName()); + person.setLastname(faker.name().lastName()); + person.setMail(faker.internet().emailAddress()); + person.setIp(faker.internet().ipV4Address()); + persons.put(Integer.valueOf(i), person); + } + return persons; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/MapPerson.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/MapPerson.java new file mode 100644 index 000000000..4dc4d124f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/MapPerson.java @@ -0,0 +1,97 @@ +package test.eclipse.store.collections.lazy.hashmap.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class MapPerson +{ + private String firstname; + private String lastname; + private String mail; + private String gender; + private String ip; + private String value1; + private String value2; + + public String getFirstname() + { + return firstname; + } + + public void setFirstname(String firstname) + { + this.firstname = firstname; + } + + public String getLastname() + { + return lastname; + } + + public void setLastname(String lastname) + { + this.lastname = lastname; + } + + public String getMail() + { + return mail; + } + + public void setMail(String mail) + { + this.mail = mail; + } + + public String getGender() + { + return gender; + } + + public void setGender(String gender) + { + this.gender = gender; + } + + public String getIp() + { + return ip; + } + + public void setIp(String ip) + { + this.ip = ip; + } + + public String getValue1() + { + return value1; + } + + public void setValue1(String value1) + { + this.value1 = value1; + } + + public String getValue2() + { + return value2; + } + + public void setValue2(String value2) + { + this.value2 = value2; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/UnloaderTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/UnloaderTest.java new file mode 100644 index 000000000..dc621e27f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashmap/unload/UnloaderTest.java @@ -0,0 +1,185 @@ +package test.eclipse.store.collections.lazy.hashmap.unload; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.awaitility.Awaitility; +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +public class UnloaderTest +{ + + @TempDir + Path location; + + SegmentStatistics loadedSegments; + + @Test + void streamTestDefault() + { + LazyHashMap personMap = HashMapGenerator.generate(200, new LazySegmentUnloader.Default(), 50); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personMap, location)) { + + personMap.keySet() + .stream() + .map(integer -> integer + 1) + .collect(Collectors.toList()); + + personMap.entrySet() + .stream() + .map(mapPersonEntry -> mapPersonEntry.toString()) + .collect(Collectors.toList()); + + Assertions.assertEquals(2, getLoadedSegments(personMap).loaded); + } + } + + @Test + @Disabled("flaky test") + void parallelStreamTestDefault() throws InterruptedException + { + LazyHashMap personMap = HashMapGenerator.generate(200, new LazySegmentUnloader.Default(), 50); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personMap, location)) { + + personMap.keySet() + .parallelStream() + .map(integer -> integer + 1) + .collect(Collectors.toList()); + + personMap.entrySet() + .parallelStream() + .map(mapPersonEntry -> mapPersonEntry.toString()) + .collect(Collectors.toList()); + + Awaitility.await() + .atMost(Duration.ofMillis(5000)) + .pollInterval(Duration.ofMillis(20)) + .untilAsserted(() -> Assertions.assertEquals(2, getLoadedSegments(personMap).loaded)); + + } + } + + @Test + void parallelStreamTestTimed() throws InterruptedException + { + LazyHashMap personMap = HashMapGenerator.generate(2000, new LazySegmentUnloader.Timed(50), 100); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personMap, location)) { + + personMap.keySet() + .parallelStream() + .map(integer -> integer + 1) + .collect(Collectors.toList()); + + personMap.entrySet() + .parallelStream() + .map(mapPersonEntry -> mapPersonEntry.toString()) + .collect(Collectors.toList()); + + Thread.sleep(80); + personMap.get(6); + + Assertions.assertEquals(1, getLoadedSegments(personMap).loaded); + } + } + + @Test + void parallelStreamTestNever() throws InterruptedException + { + LazyHashMap personMap = HashMapGenerator.generate(20_000, new LazySegmentUnloader.Never()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personMap, location)) { + + personMap.keySet() + .parallelStream() + .map(integer -> integer + 1) + .collect(Collectors.toList()); + + Assertions.assertEquals(0, getLoadedSegments(personMap).unloaded); + } + } + + @Test + void tryUnloadTestDefault() + { + LazyHashMap personMap = HashMapGenerator.generate(2000, new LazySegmentUnloader.Default(), 100); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(personMap, location)) { + + personMap.keySet() + .parallelStream() + .map(integer -> integer + 1) + .collect(Collectors.toList()); + + personMap.entrySet() + .parallelStream() + .map(mapPersonEntry -> mapPersonEntry.toString()) + .collect(Collectors.toList()); + + personMap.keySet().tryUnload(true); + //System.out.println(getLoadedSegments(personMap)); + Assertions.assertEquals(0, getLoadedSegments(personMap).loaded); + } + } + + private SegmentStatistics getLoadedSegments(LazyHashMap map) + { + final AtomicInteger loadedCount = new AtomicInteger(); + final AtomicInteger unloadedCount = new AtomicInteger(); + map.segments() + .forEach(s -> { + if (s.isLoaded()) { + loadedCount.incrementAndGet(); + } else { + unloadedCount.incrementAndGet(); + } + }); + + return new SegmentStatistics(loadedCount.get(), unloadedCount.get()); + } + + static class SegmentStatistics + { + int loaded; + int unloaded; + + public SegmentStatistics(final int loaded, final int unloaded) + { + super(); + this.loaded = loaded; + this.unloaded = unloaded; + } + + @Override + public String toString() + { + return "SegmentStatistics [loaded=" + this.loaded + ", unloaded=" + this.unloaded + "]"; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashset/HashSetStoreTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashset/HashSetStoreTest.java new file mode 100644 index 000000000..8ea0b2620 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashset/HashSetStoreTest.java @@ -0,0 +1,106 @@ +package test.eclipse.store.collections.lazy.hashset; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static test.eclipse.store.collections.lazy.hashset.Util.faker; + +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyHashSet; +import org.eclipse.serializer.collections.lazy.LazySegmentUnloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class HashSetStoreTest +{ + + @TempDir + Path location; + + @Test + void saveLazyHashSet() + { + LazyHashSet lazyHashSet = Util.generateLazyHashSet(10, 100); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(lazyHashSet, location)) { + + } + + LazyHashSet lazyHashSet1 = new LazyHashSet<>(); + try (EmbeddedStorageManager storage = EmbeddedStorage.start(lazyHashSet1, location)) { + assertIterableEquals(lazyHashSet, lazyHashSet1); + assertEquals(lazyHashSet.size(), lazyHashSet1.size()); + } + } + + @Test + void unloaderTest() throws InterruptedException + { + LazyHashSet lazyHashSet = new LazyHashSet<>(3, new LazySegmentUnloader.Timed(50)); + for (int i = 0; i < 100; i++) { + lazyHashSet.add(faker.lorem() + .sentence()); + } + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(lazyHashSet, location)) { + + Thread.sleep(800); + lazyHashSet.contains(50); + + AtomicInteger atomicInteger = new AtomicInteger(0); + + lazyHashSet.segments() + .forEach(segment -> { + if (segment.isLoaded()) { + atomicInteger.incrementAndGet(); + } + }); + assertEquals(1, atomicInteger.get()); + } + } + + @Test + void unloaderStreamTest() throws InterruptedException + { + LazyHashSet lazyHashSet = new LazyHashSet<>(3, new LazySegmentUnloader.Timed(50)); + for (int i = 0; i < 100; i++) { + lazyHashSet.add(faker.lorem() + .sentence()); + } + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(lazyHashSet, location)) { + + Stream stream = lazyHashSet.stream(); + stream.map(s -> s.toString()).collect(Collectors.toList()); + Thread.sleep(800); + lazyHashSet.contains(50); + + AtomicInteger atomicInteger = new AtomicInteger(0); + + lazyHashSet.segments() + .forEach(segment -> { + if (segment.isLoaded()) { + atomicInteger.incrementAndGet(); + } + }); + assertEquals(1, atomicInteger.get()); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashset/Util.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashset/Util.java new file mode 100644 index 000000000..36c2d9f47 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/hashset/Util.java @@ -0,0 +1,37 @@ +package test.eclipse.store.collections.lazy.hashset; + +/*- + * #%L + * MicroStream Base + * %% + * Copyright (C) 2019 - 2022 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.serializer.collections.lazy.LazyHashSet; + +import net.datafaker.Faker; + +public class Util +{ + + static Faker faker = new Faker(); + + + public static LazyHashSet generateLazyHashSet(int segmentSize, int count) + { + LazyHashSet lazyHashSet = new LazyHashSet<>(segmentSize); + for (int i = 0; i < count; i++) { + lazyHashSet.add(faker.lorem() + .sentence()); + } + return lazyHashSet; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/HashMapUnitTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/HashMapUnitTest.java new file mode 100644 index 000000000..ae2367f46 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/HashMapUnitTest.java @@ -0,0 +1,770 @@ +package test.eclipse.store.collections.lazy.unit; + +/*- + * #%L + * MicroStream Base + * %% + * Copyright (C) 2019 - 2022 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyCollection; +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazySet; +import org.eclipse.serializer.reference.Lazy; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class HashMapUnitTest +{ + + @TempDir + Path location; + + @Test + public void sizeTest() + { + final LazyHashMap map = Util.generateMap(100); + + assertEquals(100, map.size()); + + } + + @Test + public void isEmpty() + { + final LazyHashMap map = new LazyHashMap<>(); + + assertTrue(map.isEmpty()); + + } + + @Test + public void containsKey() + { + final LazyHashMap map = Util.generateMap(100); + map.put(null, null); + + assertTrue(map.containsValue(null)); + assertTrue(map.containsKey(5)); + } + + + @Test + public void containsValueNull() + { + final LazyHashMap map = Util.generateMap(100); + map.put(null, null); + + assertTrue(map.containsKey(null)); + + assertTrue(map.containsValue(null)); + assertTrue(map.containsKey(5)); + } + + + @Test + public void containsValue() + { + final String value = "Hi, i am a great value test sentence"; + final LazyHashMap map = new LazyHashMap<>(); + + map.put(1, value); + assertTrue(map.containsValue(value)); + + } + + @Test + public void getTest() + { + final String value = "Hi, i am a great value test sentence"; + final LazyHashMap map = new LazyHashMap<>(); + + map.put(1, value); + assertEquals(value, map.get(1)); + + } + + @Test + public void removeTest() + { + final String value = "Hi, i am a great value test sentence"; + final LazyHashMap map = new LazyHashMap<>(); + + map.put(1, value); + assertEquals(value, map.get(1)); + + } + + @Test + void remoteNullValueTest() + { + final LazyHashMap map = Util.generateMap(100); + map.put(101, null); + map.put(102, null); + assertEquals(102, map.size()); + map.remove(102); + assertEquals(101, map.size()); + } + + + @Test + void remoteNullValueHashMapTest() + { + final HashMap map = Util.generateHashMap(100); + map.put(101, null); + map.put(102, null); + assertEquals(102, map.size()); + map.remove(102); + assertEquals(101, map.size()); + } + + @Test + public void putAll(@TempDir final Path secondLocation) + { + final LazyHashMap map = Util.generateMap(100); + + + final LazyHashMap secondMap = Util.generateMap(100, 100); + + map.putAll(secondMap); + + } + + @Test + public void putALLtoOneStorage() + { + final LazyHashMap map = Util.generateMap(100); + + + final LazyHashMap secondMap = Util.generateMap(100, 100); + + map.putAll(secondMap); + + + } + + /** + * Just test to prove behavior with no-lazy collections. + * + * @param secondLocation Junit feature to provide private folder in temp directory + */ + @Test + public void lazyTwoStorageTest(@TempDir final Path secondLocation) + { + + final ArrayList> lazyList = Stream.generate(() -> { + final String type1 = new String("ahoj"); + return Lazy.Reference(type1); + }) + .limit(1000) + .collect(Collectors.toCollection(ArrayList::new)); + + + } + + @Test + public void clear() + { + final String value = "Hi, i am a great value test sentence"; + final LazyHashMap map = Util.generateMap(100); + + + map.clear(); + + assertTrue(map.isEmpty()); + + + } + + + /** + * After fix diese Issue, add some other tests: terator.remove, Set.remove, removeAll, retainAll, and clear operations + */ + @Test + public void keySetRemove() + { + + final LazyHashMap map = Util.generateMap(100); + + map.put(101, "some text"); + map.keySet() + .remove(101); + + } + + @Test + public void keySet() + { + + final LazyHashMap map = Util.generateMap(100); + + + final Set keySet = map.keySet(); + assertEquals(100, keySet.size()); + map.put(101, "some text"); + assertEquals(101, keySet.size()); + + + map.put(102, "another text"); + final Set map1KeySet = map.keySet(); + assertEquals(102, keySet.size()); + assertEquals(102, map1KeySet.size()); + + + } + + @Test + public void values_clear() + { + final LazyHashMap map = Util.generateMap(100); + final LazyCollection values = map.values(); + values.clear(); + assertTrue(map.isEmpty()); + + } + + @Test + public void values_removeAll() + { + LazyHashMap map = Util.generateMap(100); + final LazyCollection values = map.values(); + values.removeAll(map.values()); + assertTrue(map.isEmpty()); + + map = Util.generateMap(100); + + } + + @Test + public void entrySet() + { + final LazyHashMap map = Util.generateMap(100); + final LazySet> entries = map.entrySet(); + assertEquals(100, entries.size()); + + } + + @Test + void entryTest() + { + final String value = "Ahoj"; + + final LazyHashMap map = Util.generateMap(100); + final LazySet> entries = map.entrySet(); + for (final Map.Entry entry : entries) { + assertThrows(UnsupportedOperationException.class, () -> entry.setValue(value)); + } + + + final LazySet> entries1 = map.entrySet(); + for (final Map.Entry integerStringEntry : entries1) { + assertTrue(integerStringEntry.getValue() + .length() > 0); + assertTrue(integerStringEntry.getKey() > -1); + } + } + + + @Test + void replaceAll() + { + final LazyHashMap map = Util.generateMap(100); + + assertThrows(UnsupportedOperationException.class, () -> map.replaceAll((key, oldValue) -> { + return oldValue + oldValue; + })); + } + + @Test + void putIfAbsent() + { + final String s = "javax.net.ssl.keyStore"; + + final LazyHashMap map = Util.generateMap(100); + final String value = map.get(50); + map.put(50, null); + map.putIfAbsent(50, s); + + assertEquals(s, map.get(50)); + + } + + @Test + void remove_for_specific_key_and_value() + { + final LazyHashMap map = Util.generateMap(100); + final String value = map.get(60); + assertFalse(map.remove(60, "javax.net.ssl.keyStore")); + assertTrue(map.remove(60, value)); + assertEquals(99, map.size()); + } + + + @Test + void computeIfAbsent() + { + final String value = "ahoj"; + final LazyHashMap map = Util.generateMap(100); + map.put(101, null); + + map.computeIfAbsent(101, v -> value); + + assertEquals(value, map.get(101)); + } + + @Test + void computeIfPresent() + { + final String value = "ahoj"; + final LazyHashMap map = Util.generateMap(100); + map.put(101, value); + map.computeIfPresent(101, (k, v) -> v + value); + assertEquals(value + value, map.get(101)); + } + + @Test + void merge() + { + final String value = "ahoj"; + final LazyHashMap map = Util.generateMap(100); + map.put(101, value); + map.compute(101, (k, v) -> v + value); + assertEquals(value + value, map.get(101)); + + map.merge(101, value, (k, v) -> v + v); + assertEquals(value + value, map.get(101)); + + } + + @Test + void ofTestEmpty() + { + final LazyHashMap map = Util.generateMap(100); + final Map immutableMap = Map.of(); + map.putAll(immutableMap); + assertEquals(100, map.size()); + } + + @Test + void ofTestWithValue() + { + final LazyHashMap map = Util.generateMap(100); + final Map immutableMap = Map.of(101, "PP", 102, "QQ", 103, "RR"); + map.putAll(immutableMap); + assertEquals(103, map.size()); + } + + @Test + void ofEntries() + { + final LazyHashMap map = Util.generateMap(100); + final Map immutableMap = Map.ofEntries(Map.entry(101, "ahoj"), + Map.entry(102, "ahoj2"), Map.entry(103, "ahoj3")); + map.putAll(immutableMap); + assertEquals(103, map.size()); + } + + @Test + void entry() + { + final LazyHashMap map = Util.generateMap(100); + final Map.Entry ahoj = Map.entry(101, "ahoj"); + assertThrows(UnsupportedOperationException.class, () -> map.entrySet() + .add(ahoj)); + + } + + @Test + void copyOf() + { + final LazyHashMap map = Util.generateMap(100); + final Map integerStringMap = Map.copyOf(map); + assertEquals(100, map.size()); + } + + @Test + void replaceWithOldValue_notExists() + { + final LazyHashMap map = new LazyHashMap<>(); + assertFalse(map.replace(5, "oldValue", "newValue")); + } + + @Test + void replaceWithOldValue() + { + final LazyHashMap map = Util.generateMap(10); + final String oldValue = map.get(5); + assertTrue(map.replace(5, oldValue, "newValue")); + } + + @Test + void removeSegmentIfEmpty() + { + final LazyHashMap map = Util.generateMap(100); + for (int i = 0; i < map.size(); i++) { + map.remove(i); + } + } + + @Test + void lazyMapIterator() + { + final LazyHashMap map = Util.generateMap(100); + map.entrySet() + .removeIf(e -> true); + } + + /* Segment */ + + @Test + void isSegmentLoaded() + { + final LazyHashMap map = Util.generateMap(100); + map.segments() + .forEach(segment -> { + assertTrue(segment.isLoaded()); + }); + } + + @Test + void isSegmentModified() + { + final LazyHashMap map = Util.generateMap(100); + map.segments() + .forEach(segment -> { + assertTrue(segment.isModified()); + }); + } + + @Test + void unloadSegment() + { + final LazyHashMap map = Util.generateMap(100); + map.segments() + .forEach(LazyHashMap.Segment::unloadSegment); + map.segments() + .forEach(segment -> { + assertTrue(segment.isLoaded()); + }); + } + + @Test + void constructorWithMaxSegmentSize() + { + final LazyHashMap map = new LazyHashMap<>(100); + assertEquals(100, map.getMaxSegmentSize()); + assertNotNull(map); + } + + @Test + void constructorWithMaxSegmentSize_0() + { + LazyHashMap map = new LazyHashMap<>(0); + assertEquals(0, map.getMaxSegmentSize()); + + map = Util.fillHashMap(map, 100, 0); + + assertNotNull(map); + } + + @Test + void constructorFromExistingLazyHashMap() + { + LazyHashMap map = new LazyHashMap<>(100); + map = Util.fillHashMap(map, 200, 0); + final LazyHashMap newMap = new LazyHashMap<>(map); + assertEquals(map.size(), newMap.size()); + assertTrue(map.getSegmentCount() > 1); + } + + @Test + void segmentsTest() + { + final LazyHashMap map = Util.generateMap(100); + final Iterable.Segment> segments = map.segments(); + assertNotNull(segments); + } + + @Test + void maxSegmentsSize() + { + final LazyHashMap map = new LazyHashMap<>(100); + assertEquals(100, map.getMaxSegmentSize()); + } + + + @Test + void copyTest() throws CloneNotSupportedException + { + final LazyHashMap map = new LazyHashMap<>(); + map.put(1, "ahoj"); + map.put(2, "second string"); + + final LazyHashMap clone = new LazyHashMap<>(map); + + assertIterableEquals(map.entrySet(), clone.entrySet()); + } + + + @Test + void containsKeyTest() + { + final LazyHashMap map = Util.generateMap(100); + map.put(null, null); + assertTrue(map.containsKey(null)); + } + + @Test + void getWhatNotExists() + { + final LazyHashMap map = new LazyHashMap<>(); + assertNull(map.get(5)); + } + + @Test + void containsValue_notExists() + { + final LazyHashMap map = new LazyHashMap<>(); + assertFalse(map.containsValue("something")); + } + + @Test + void remove_notExists() + { + final LazyHashMap map = new LazyHashMap<>(); + assertNull(map.remove(5)); + } + + @Test + void replaceTest_nonExists() + { + final LazyHashMap map = new LazyHashMap<>(); + assertNull(map.replace(5, "ahoj")); + } + + @Test + void replace() + { + final LazyHashMap map = Util.generateMap(20); + final String origValue = map.get(5); + assertEquals(origValue, map.replace(5, "someText")); + } + + @Test + void valuesSplitIterator() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.values().spliterator()); + + assertTrue(spliterators.size() > 1); + } + + @Test + void keysSplitIterator() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.keySet() + .spliterator()); + assertTrue(spliterators.size() > 1); + for (final Spliterator spliterator : spliterators) { + spliterator.tryAdvance(Object::toString); + } + } + + @Test + void keysSplitIteratorReverse() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.keySet() + .spliterator()); + assertTrue(spliterators.size() > 1); + for (int i = spliterators.size(); i-- > 0; ) { + final Spliterator spliterator = spliterators.get(i); + spliterator.tryAdvance(Object::toString); + } + } + + @Test + void keysSplitIteratorNullAction() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.keySet() + .spliterator()); + assertTrue(spliterators.size() > 1); + for (final Spliterator spliterator : spliterators) { + assertThrows(NullPointerException.class, () -> spliterator.tryAdvance(null)); + } + } + + @Test + void keysSplitIteratorConcurrentAction() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.keySet() + .spliterator()); + assertTrue(spliterators.size() > 1); + map.put(101, "last item"); + for (final Spliterator spliterator : spliterators) { + assertThrows(ConcurrentModificationException.class, () -> spliterator.tryAdvance(Object::toString)); + } + } + + @Test + void splitIteratorTest() + { + final LazyHashMap map = Util.generateMap(100); + final Spliterator spliterator = map.values() + .spliterator(); + final Spliterator split2 = spliterator.trySplit(); + + assertTrue(split2.estimateSize() > 0); + ; + } + + @Test + void entrySplitIterator() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.entrySet() + .spliterator()); + assertTrue(spliterators.size() > 1); + for (final Spliterator spliterator : spliterators) { + spliterator.tryAdvance(Object::toString); + } + } + + @Test + void valueSplitIterator() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.values() + .spliterator()); + assertTrue(spliterators.size() > 1); + for (final Spliterator spliterator : spliterators) { + spliterator.tryAdvance(Object::toString); + } + } + + @Test + void segmentsSplitIterator() + { + final LazyHashMap map = Util.generateMap(100); + final List> spliterators = this.splitAll(map.segments() + .spliterator()); + assertTrue(spliterators.size() > 1); + for (final Spliterator spliterator : spliterators) { + spliterator.tryAdvance(Object::toString); + } + } + + private List> splitAll(final Spliterator spliterator) + { + final List> spliterators = new ArrayList<>(); + final Spliterator split = spliterator.trySplit(); + + if (split != null) { + spliterators.addAll(this.splitAll(spliterator)); + spliterators.addAll(this.splitAll(split)); + } else { + spliterators.add(spliterator); + } + + return spliterators; + } + + @Test + void valueIteratorTest() + { + final LazyHashMap map = Util.generateMap(100); + for (final String value : map.values()) { + assertNotNull(value); + } + } + + @Test + void keyIteratorTest() + { + final LazyHashMap map = Util.generateMap(100); + for (final Integer integer : map.keySet()) { + assertNotNull(integer); + } + } + + @Test + void entryIteratorTest() + { + final LazyHashMap map = Util.generateMap(100); + for (final Map.Entry integerStringEntry : map.entrySet()) { + assertNotNull(integerStringEntry); + } + } + + @Test + void toStringEmptyMap() + { + final LazyHashMap map = new LazyHashMap<>(); + assertEquals("{}", map.toString()); + } + + @Test + void entryHashCode() + { + final LazyHashMap map = Util.generateMap(100); + final LazySet> entries = map.entrySet(); + for (final Map.Entry entry : entries) { + assertNotNull(entry.hashCode()); + break; + } + } + + @Test + void entrySetClear() + { + final LazyHashMap map = Util.generateMap(100); + map.entrySet().clear(); + assertEquals(0, map.size()); + } + + @Test + void entrySet_iterateLazyReference() + { + final LazyHashMap map = Util.generateMap(100); + final LazySet> entries = map.entrySet(); + entries.iterateLazyReferences((l) -> { + assertTrue(l.isLoaded()); + }); + } + + @Test + void entrySet_consolidate() + { + final LazyHashMap map = Util.generateMap(100); + final LazySet> entries = map.entrySet(); + entries.consolidate(); + assertEquals(100, map.size()); + } + + @Test + void value_iterateLazyReference() + { + final LazyHashMap map = Util.generateMap(100); + map.values().iterateLazyReferences((v) -> { + assertTrue(v.isLoaded()); + }); + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/HashSetUnitTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/HashSetUnitTest.java new file mode 100644 index 000000000..c157e3e19 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/HashSetUnitTest.java @@ -0,0 +1,91 @@ +package test.eclipse.store.collections.lazy.unit; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.serializer.collections.lazy.LazyHashSet; +import org.junit.jupiter.api.Test; + +public class HashSetUnitTest +{ + + @Test + void lazyHashSetConstructor() + { + LazyHashSet lazyHashSet = new LazyHashSet<>(); + assertNotNull(lazyHashSet); + } + + @Test + void lazyHashSetConstructor_maxSegmentSize() + { + LazyHashSet lazyHashSet = new LazyHashSet<>(10); + assertNotNull(lazyHashSet); + } + + @Test + void iteratorTest() + { + LazyHashSet lazyHashSet = Util.generateLazyHashSet(10, 100); + for (String s : lazyHashSet) { + assertNotNull(s); + } + } + + @Test + void sizeTest() + { + LazyHashSet lazyHashSet = Util.generateLazyHashSet(10, 100); + assertEquals(100, lazyHashSet.size()); + } + + @Test + void isEmpty() + { + LazyHashSet lazyHashSet = new LazyHashSet<>(); + assertTrue(lazyHashSet.isEmpty()); + lazyHashSet = Util.generateLazyHashSet(10, 100); + assertFalse(lazyHashSet.isEmpty()); + } + + @Test + void contains() + { + String value = "Hi Microstream"; + + LazyHashSet lazyHashSet = Util.generateLazyHashSet(10, 100); + lazyHashSet.add(value); + assertTrue(lazyHashSet.contains(value)); + } + + @Test + void remove() + { + String value = "Hi Microstream"; + LazyHashSet lazyHashSet = Util.generateLazyHashSet(10, 100); + lazyHashSet.add(value); + assertTrue(lazyHashSet.remove(value)); + assertEquals(100, lazyHashSet.size()); + } + + @Test + void clear() + { + LazyHashSet lazyHashSet = Util.generateLazyHashSet(10, 100); + lazyHashSet.clear(); + assertTrue(lazyHashSet.isEmpty()); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/LazyArrayListUnitTest.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/LazyArrayListUnitTest.java new file mode 100644 index 000000000..e09ee3dd2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/LazyArrayListUnitTest.java @@ -0,0 +1,729 @@ +package test.eclipse.store.collections.lazy.unit; + +/*- + * #%L + * MicroStream Base + * %% + * Copyright (C) 2019 - 2022 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class LazyArrayListUnitTest +{ + + @Test + public void addItem() + { + LazyArrayList list = new LazyArrayList<>(); + + assertTrue(list.isEmpty()); + assertEquals(0, list.size()); + assertTrue(list.add(1)); + assertEquals(1, list.size()); + assertEquals(1, list.get(0)); + + assertTrue(list.addAll(list)); + assertEquals(2, list.size()); + + assertTrue(list.add(null)); + assertEquals(3, list.size()); + } + + @Test + void addAll() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + LazyArrayList list = new LazyArrayList<>(); + assertFalse(lazyArrayList.addAll(list)); + } + + @Test + void addAllEmpty() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + LazyArrayList list2 = new LazyArrayList<>(); + lazyArrayList.addAll(0, list2); + assertEquals(100, lazyArrayList.size()); + } + + @Test + void addAllToEnd() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + LazyArrayList list2 = Util.generateLazyArrayList(10, 100); + lazyArrayList.addAll(100, list2); + assertEquals(200, lazyArrayList.size()); + } + + @Test + void addAllInside() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + LazyArrayList list2 = Util.generateLazyArrayList(10, 100); + lazyArrayList.addAll(50, list2); + assertEquals(200, lazyArrayList.size()); + } + + + @Test + void addItemSimple() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + lazyArrayList.add(100, "SomeString"); + assertEquals(101, lazyArrayList.size()); + } + + @Test + void getSegmentCountTest() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + assertEquals(10, lazyArrayList.getSegmentCount()); + } + + @Test + void iterateSegmentsTest() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + for (LazyArrayList.Segment segment : lazyArrayList.segments()) { + assertTrue(segment.isLoaded()); + } + } + + @Test + void getMaxSegmentSize() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + assertEquals(10, lazyArrayList.getMaxSegmentSize()); + } + + @Test + public void isEmpty() + { + LazyArrayList list = new LazyArrayList<>(); + assertTrue(list.isEmpty()); + } + + @Test + public void contains() + { + String s = "Hello, i would to be a great object"; + + LazyArrayList list = new LazyArrayList<>(); + list.add(s); + + assertTrue(list.contains(s)); + + } + + @Test + public void containsHuge() + { + LazyArrayList list = Stream.generate(() -> "Java") + .limit(35000) + .collect(Collectors.toCollection(LazyArrayList::new)); + + assertTrue(list.contains("Java")); + + } + + @Test + public void iterator() + { + String s = "Hello, i would to be a great object"; + + LazyArrayList list = new LazyArrayList<>(); + list.add(s); + list.add(s); + list.add(s); + + ListIterator listIterator = list.listIterator(); + + int i = 0; + + while (listIterator.hasNext()) { + i++; + listIterator.next(); + } + + assertEquals(3, i); + } + + @Test + public void toArray() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + Object[] intArray = list.toArray(); + + Object[] expectIntArray = {1, 2, 3}; + assertArrayEquals(expectIntArray, intArray); + } + + @Test + public void toArrayWithType() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + Integer[] intArray = list.toArray(new Integer[0]); + + Integer[] expectIntArray = {1, 2, 3}; + assertArrayEquals(expectIntArray, intArray); + } + + @Test + public void toArrayWithTypeLongerArray() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + Integer[] intArray = list.toArray(new Integer[5]); + assertEquals(5, intArray.length); + Integer[] expectIntArray = {1, 2, 3, null, null}; + assertArrayEquals(expectIntArray, intArray); + } + + @Test + public void remove() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + list.remove(2); + + assertEquals(2, list.size()); + } + + @Test + void removeObject() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + String s = lazyArrayList.get(50); + assertTrue(lazyArrayList.remove(s)); + assertEquals(99, lazyArrayList.size()); + } + + @Test + void removeAllSameObject() + { + LazyArrayList lazyArrayList = Stream.generate(() -> "Java") + .limit(100) + .collect(Collectors.toCollection(LazyArrayList::new)); + assertTrue(lazyArrayList.remove("Java")); + assertEquals(99, lazyArrayList.size()); + } + + @Test + void removeSegmentIndex() + { + LazyArrayList list = Util.generateLazyArrayList(10, 101); + assertNotNull(list.remove(100)); + } + + @Test + void removeSegmentValue() + { + LazyArrayList list = Util.generateLazyArrayList(10, 101); + String s = list.get(100); + assertNotNull(list.remove(s)); + } + + + @Test + void removeObject_notExists() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + String s = "fkldsj;f jadslkf jsdalkjf alsdjf ;aksjfld"; + assertFalse(lazyArrayList.remove(s)); + assertEquals(100, lazyArrayList.size()); + } + + @Test + void removeIfTest() + { + LazyArrayList lazyArrayList = Stream.generate(() -> "Java") + .limit(100) + .collect(Collectors.toCollection(LazyArrayList::new)); + assertTrue(lazyArrayList.removeIf((v) -> v.equals("Java"))); + assertTrue(lazyArrayList.isEmpty()); + } + + @Test + void consolidate() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + lazyArrayList.remove(54); + lazyArrayList.remove(82); + assertTrue(lazyArrayList.consolidate()); + } + + @Test + void iterateLazyReferences() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + lazyArrayList.iterateLazyReferences((i) -> { + assertTrue(i.isLoaded()); + }); + } + + @Test + void loadedFirstIterator() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + for (Iterator it = lazyArrayList.loadedFirstIterator(); it.hasNext(); ) { + String stringIterator = it.next(); + assertNotNull(stringIterator); + } + for (Iterator it = lazyArrayList.loadedFirstIterator(); it.hasNext(); ) { + String stringIterator = it.next(); + assertNotNull(stringIterator); + } + } + + @Test + void loadedFirstIterator_remove() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + for (Iterator it = lazyArrayList.loadedFirstIterator(); it.hasNext(); ) { + String stringIterator = it.next(); + it.remove(); + } + assertTrue(lazyArrayList.isEmpty()); + } + + @Test + void spliteratorCharacteristics() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + assertNotEquals(0, lazyArrayList.spliterator() + .characteristics()); + } + + @Test + void segmentsSpliteratorCharacteristics() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + assertNotEquals(0, lazyArrayList.segmentSpliterator() + .characteristics()); + } + + @Test + void segmentSpliteratorTest() + { + LazyArrayList lazyArrayList = Util.generateLazyArrayList(10, 100); + Spliterator stringSpliterator = lazyArrayList.segmentSpliterator(); + assertTrue(stringSpliterator.tryAdvance(Assertions::assertNotNull)); + } + + @Test + public void containsAll() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + assertTrue(list.containsAll(list2)); + } + + @Test + public void addAllIndex() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + assertTrue(list.addAll(2, list2)); + + List list3 = new LazyArrayList<>(); + list3.add(1); + list3.add(2); + list3.add(1); + list3.add(2); + list3.add(3); + list3.add(3); + + assertIterableEquals(list3, list); + } + + @Test + public void removeAllCollection() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + assertTrue(list.removeAll(list2)); + + assertTrue(list.isEmpty()); + } + + @Test + public void retainAll() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(3); + + assertTrue(list.retainAll(list2)); + + List resultList = new LazyArrayList<>(); + resultList.add(1); + resultList.add(3); + + assertIterableEquals(resultList, list); + } + + + @Test + public void replaceAllUnaryOperator() + { + class Op implements UnaryOperator + { + public String apply(String str) + { + return str.toUpperCase(); + } + } + + List list = new LazyArrayList<>(); + list.add("Java"); + list.add("JavaScript"); + //System.out.println("Contents of the list: " + list); + list.replaceAll(new Op()); + + List expectedList = new LazyArrayList<>(); + expectedList.add("JAVA"); + expectedList.add("JAVASCRIPT"); + + assertIterableEquals(expectedList, list); + } + + @Test + public void sort() + { + + Comparator valueComparator = (Integer o1, Integer o2) -> o1 - o2; + + List list = new LazyArrayList<>(); + list.add(3); + list.add(2); + list.add(1); + + list.sort(valueComparator); + + List resultList = new LazyArrayList<>(); + resultList.add(1); + resultList.add(2); + resultList.add(3); + + assertIterableEquals(resultList, list); + } + + @Test + public void clear() + { + List list = new LazyArrayList<>(); + list.add(3); + list.add(2); + list.add(1); + + list.clear(); + + assertTrue(list.isEmpty()); + } + + @Test + public void equals() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + assertTrue(list.equals(list2)); + + + list2.add(4); + assertFalse(list.equals(list2)); + } + + @Test + public void hashCodeTest() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + List list2 = new ArrayList<>(); + list2.add(1); + list2.add(2); + list2.add(3); + + assertEquals(list2.hashCode(), list.hashCode()); + + list2.add(4); + assertNotEquals(list2.hashCode(), list.hashCode()); + } + + @Test + public void get() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + assertEquals(2, list.get(1)); + } + + @Test + public void getWithIndexOutOfBoundException() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + assertAll("OutOfBoundException tests", + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.get(20)), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.get(-5)), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.get(list.size()))); + } + + @Test + public void setAndGetBack() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + assertEquals(3, list.set(2, 100)); + + List resultList = new LazyArrayList<>(); + resultList.add(1); + resultList.add(2); + resultList.add(100); + + assertIterableEquals(resultList, list); + } + + @Test + public void setIndexBoundOfException() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + assertAll("OutOfBoundException tests", + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.set(20, 50)), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.set(-5, 50)), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.set(list.size(), 50)) + ); + } + + @Test + public void addWithIndex() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + list.add(0, 100); + + List resultList = new LazyArrayList<>(); + resultList.add(100); + resultList.add(1); + resultList.add(2); + resultList.add(3); + + assertIterableEquals(resultList, list); + } + + @Test + public void removeWithIndex() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + assertEquals(2, list.remove(1)); + + List resultList = new LazyArrayList<>(); + resultList.add(1); + resultList.add(3); + + assertIterableEquals(resultList, list); + } + + + @Test + public void indexOf() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + assertEquals(1, list.indexOf(2)); + } + + @Test + public void lastIndexOf() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(1); + list.add(1); + + assertAll("lastIndexOf", + () -> assertEquals(2, list.lastIndexOf(1)), + () -> assertEquals(-1, list.lastIndexOf(-50))); + } + + @Test + public void listIteratorIndex() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(1); + list.add(1); + + ListIterator listIterator = list.listIterator(1); + + int i = 0; + + while (listIterator.hasNext()) { + i++; + listIterator.next(); + } + + assertEquals(2, i); + } + + @Test + public void sublist() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + list.add(4); + list.add(5); + + List sublist = list.subList(2, 4); + + List resultList = new LazyArrayList<>(); + resultList.add(3); + resultList.add(4); + + assertAll("Method Sublist", + () -> assertIterableEquals(resultList, sublist), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.subList(-1, 5)), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.subList(1, 50)), + () -> assertThrows(IndexOutOfBoundsException.class, () -> list.subList(100, 50))); + } + + public LazyArrayList
generateElements() + { + return Stream.generate(() -> new Article("Java")) + .limit(35000) + .collect(Collectors.toCollection(LazyArrayList::new)); + } + + @Test + public void givenSpliterator_whenAppliedToAListOfArticle_thenSplittedInHalf() + { + LazyArrayList
articles = generateElements(); + Spliterator
split1 = articles.spliterator(); + Spliterator
split2 = split1.trySplit(); + + assertAll("splitIterator tests", + () -> assertEquals(35000, articles.size()), + () -> assertEquals(17000, split1.estimateSize()), + () -> assertEquals(18000, split2.estimateSize())); + } + + public class Article + { + private int id; + private String name; + + public Article(String name) + { + this.name = name; + } + + // standard constructors/getters/setters + } + + + @Test + public void copyOf() + { + List list = new LazyArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + list.add(4); + list.add(5); + + List resultList = List.copyOf(list); + + assertIterableEquals(list, resultList); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/Util.java b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/Util.java new file mode 100644 index 000000000..42b07b3ab --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/collections/lazy/unit/Util.java @@ -0,0 +1,101 @@ +package test.eclipse.store.collections.lazy.unit; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.ArrayList; +import java.util.HashMap; + +import org.eclipse.serializer.collections.lazy.LazyArrayList; +import org.eclipse.serializer.collections.lazy.LazyHashMap; +import org.eclipse.serializer.collections.lazy.LazyHashSet; +import org.junit.jupiter.api.Assertions; + +import net.datafaker.Faker; + +public class Util +{ + + static Faker faker = new Faker(); + + public static LazyHashMap generateMap(final Integer count) + { + return generateMap(count, 0); + } + + public static LazyHashMap generateMap(final Integer count, final int keyStart) + { + + final LazyHashMap map = new LazyHashMap<>(10); + + return fillHashMap(map, count, keyStart); + } + + public static LazyHashMap fillHashMap(final LazyHashMap map, final int count, final int keyStart) + { + for (int i = 0; i < count; i++) { + map.put(i + keyStart, faker.lorem() + .sentence()); + } + return map; + } + + public static HashMap generateHashMap(final Integer count) + { + return generateHashMap(count, 0); + } + + public static HashMap generateHashMap(final Integer count, final int keyStart) + { + + final HashMap map = new HashMap<>(); + + + for (int i = 0; i < count; i++) { + map.put(i + keyStart, faker.lorem() + .sentence()); + } + + return map; + } + + public static LazyArrayList generateLazyArrayList(final int segmentSize, final int count) + { + final LazyArrayList list = new LazyArrayList<>(segmentSize); + for (int i = 0; i < count; i++) { + list.add(faker.lorem().sentence()); + } + return list; + } + + public static ArrayList generateArrayList(final int count) + { + final ArrayList list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + list.add(faker.lorem().sentence()); + } + return list; + } + + public static LazyHashSet generateLazyHashSet(final int segmentSize, final int count) + { + final LazyHashSet lazyHashSet = new LazyHashSet<>(segmentSize); + for (int i = 0; i < count; i++) { + lazyHashSet.add(i + faker.lorem().sentence()); + } + Assertions.assertEquals(count, lazyHashSet.size(), "Size of LazyHashSet is not as expected"); + return lazyHashSet; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/BackupDirectoryTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/BackupDirectoryTest.java new file mode 100644 index 000000000..2aae73210 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/BackupDirectoryTest.java @@ -0,0 +1,270 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.afs.types.AFileSystem; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.eclipse.store.storage.types.StorageBackupFileProvider; +import org.eclipse.store.storage.types.StorageBackupSetup; +import org.eclipse.store.storage.types.StorageLiveFileProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.types.BasicNonPrimitive; +import test.eclipse.serializer.fixtures.types.BasicNonPrimitiveArrayTypes; + +class BackupDirectoryTest +{ + + @TempDir + Path location; + + @TempDir + Path backup; + + Path configFilePath; + + EmbeddedStorageManager storageManager; + + @AfterEach + public void closeStorage() + { + if (this.storageManager != null) { + if (!this.storageManager.isShutdown()) { + this.storageManager.shutdown(); + } + } + } + + + /** + * https://github.com/microstream-one/microstream/issues/197 + * + * @param secondBackup + * @throws InterruptedException + */ + @Test + public void backup_in_another_folder_as_original(@TempDir Path secondBackup) throws InterruptedException + { + + final BasicNonPrimitive basicNonPrimitive = new BasicNonPrimitive(); + basicNonPrimitive.fillSampleData(); + + storageManager = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(location.toAbsolutePath().toString()) + .setBackupDirectory(backup.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation() + .start(basicNonPrimitive); + + this.storageManager.shutdown(); + + List firstBackupFiles = (List) FileUtils.listFiles(backup.toFile(), null, true); + assertTrue(firstBackupFiles.size() > 1, firstBackupFiles.toString()); + + + // second start + + storageManager = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(location.toAbsolutePath().toString()) + .setBackupDirectory(secondBackup.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation() + .start(); + + storageManager.shutdown(); + + List secondBackupFiles = (List) FileUtils.listFiles(secondBackup.toFile(), null, true); + + StringBuilder buffer = new StringBuilder(); + secondBackupFiles.forEach(buffer::append); + assertTrue(buffer.toString().contains("PersistenceTypeDictionary.ptd"), "Check if the type dictionary is in backup folder"); + + assertTrue(secondBackupFiles.size() > 1, secondBackupFiles.toString()); + } + + @Test + public void backup_without_configuration_layer_Test() throws InterruptedException + { + + final AFileSystem aFileSystem = NioFileSystem.New(); + final ADirectory backupDir = aFileSystem.ensureDirectoryPath(this.backup.toFile().getAbsolutePath()); + final ADirectory dataDir = aFileSystem.ensureDirectoryPath(this.location.toFile().getAbsolutePath()); + + final StorageBackupSetup backupSetup = StorageBackupSetup.New( + StorageBackupFileProvider.New(backupDir) + ); + + final StorageLiveFileProvider provider = StorageLiveFileProvider.New(dataDir); + + final EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setBackupSetup(backupSetup) + .setStorageFileProvider(provider) + .createConfiguration() + + ); + + final BasicNonPrimitive basicNonPrimitive = new BasicNonPrimitive(); + basicNonPrimitive.fillSampleData(); + + this.storageManager = foundation.createEmbeddedStorageManager(basicNonPrimitive).start(); + this.storageManager.shutdown(); + + + final List files = (List) FileUtils.listFiles(backup.toFile(), null, true); +// System.out.println("file sizes: " + files.size() + ""); +// files.forEach(System.out::println); + + assertTrue(files.size() > 1, files.toString()); + + + } + + @Test + void backup_without_configuration_level() throws InterruptedException + { + this.location = this.location.resolve("backup_without_configuration_level"); + final Path backupPath = this.location.resolve("backup"); + + final AFileSystem aFileSystem = NioFileSystem.New(); + final ADirectory backupDir = aFileSystem.ensureDirectoryPath(backupPath.toFile().getAbsolutePath()); + + final ADirectory storageDir = aFileSystem.ensureDirectoryPath(this.location.toFile().getAbsolutePath()); + + final StorageBackupSetup backupSetup = StorageBackupSetup.New( + StorageBackupFileProvider.New(backupDir) + ); + + final EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setBackupSetup(backupSetup) + .setStorageFileProvider(StorageLiveFileProvider.New(storageDir)) + .createConfiguration() + ); + + final BasicNonPrimitiveArrayTypes basicNonPrimitiveArrayTypes = new BasicNonPrimitiveArrayTypes(); + basicNonPrimitiveArrayTypes.fillSampleData(); + + this.storageManager = foundation.createEmbeddedStorageManager(basicNonPrimitiveArrayTypes).start(); + + this.storageManager.shutdown(); + + final List files = (List) FileUtils.listFiles(backupPath.toFile(), null, true); +// System.out.println("file sizes: " + files.size() + ""); +// files.forEach(System.out::println); + + assertTrue(files.size() > 2, files.toString()); + + + } + + @Test + void backupDirectoryTest(@TempDir Path configPath) throws IOException, InterruptedException + { + this.configFilePath = configPath.resolve("backupDirectory.ini"); + + FileUtils.writeStringToFile(this.configFilePath.toFile(), "backup-directory = " + this.backup.toString(), "UTF-8"); + + final List customers = new ArrayList<>(); + customers.addAll(CustomerGenerator.generateCustomers(300)); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + this.storageManager = configuration.setStorageDirectory(location.toString()).createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start(); + + this.storageManager.shutdown(); + + final List files = (List) FileUtils.listFiles(this.backup.toFile(), null, true); + + assertTrue(files.size() > 2, files.toString()); + + + } + + @Test + void backupDirectoryXMLTest() throws IOException, InterruptedException + { + this.configFilePath = this.location.resolve("backupDirectory.xml"); + final Path backupLocation = this.location.resolve("backup"); + backupLocation.toFile().mkdir(); + + final StringBuilder builder = new StringBuilder() + .append("\n") + .append("\n") + .append("\t\n") + .append(""); + + FileUtils.writeStringToFile(this.configFilePath.toFile(), builder.toString(), "UTF-8"); + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + this.storageManager = configuration.setStorageDirectory(this.location.toString()).createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + this.storageManager.shutdown(); + + final List files = (List) FileUtils.listFiles(backupLocation.toFile(), null, true); + + assertTrue(files.size() > 2, files.toString()); + + } + + @Test + void backupDirectoryXMLFullTest() throws IOException, InterruptedException + { + this.configFilePath = this.location.resolve("backupDirectory.xml"); + + final String xmlConfig = "\n" + + "\n" + + "\t\n" + + "\t\n" + + ""; + FileUtils.writeStringToFile(this.configFilePath.toFile(), xmlConfig, "UTF-8"); + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + this.storageManager = configuration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + this.storageManager.shutdown(); + + final List files = (List) FileUtils.listFiles(this.backup.toFile(), null, true); + + assertTrue(files.size() > 1, files.toString()); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/ChannelCountTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/ChannelCountTest.java new file mode 100644 index 000000000..927cc18a0 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/ChannelCountTest.java @@ -0,0 +1,183 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.hocon.types.ConfigurationParserHocon; +import org.eclipse.serializer.configuration.types.ConfigurationLoader; +import org.eclipse.serializer.configuration.yaml.types.ConfigurationParserYaml; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ChannelCountTest +{ + + @TempDir + Path location; + + @Test + void channelCount4Test() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/channelCount4.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + + assertTrue(builder.toString().contains("transactions_3.sft")); + assertTrue(builder.toString().contains("transactions_2.sft")); + assertTrue(builder.toString().contains("transactions_1.sft")); + assertTrue(builder.toString().contains("transactions_0.sft")); + storageManager.shutdown(); + + } + + @Test + void channelCount4YmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + ConfigurationLoader.New("configuration/channelCount4.yml"), + ConfigurationParserYaml.New() + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + + assertTrue(builder.toString().contains("transactions_3.sft")); + assertTrue(builder.toString().contains("transactions_2.sft")); + assertTrue(builder.toString().contains("transactions_1.sft")); + assertTrue(builder.toString().contains("transactions_0.sft")); + storageManager.shutdown(); + + } + + @Test + void channelCount4HoconTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + ConfigurationLoader.New("configuration/channelCount4.json"), + ConfigurationParserHocon.New() + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + + assertTrue(builder.toString().contains("transactions_3.sft")); + assertTrue(builder.toString().contains("transactions_2.sft")); + assertTrue(builder.toString().contains("transactions_1.sft")); + assertTrue(builder.toString().contains("transactions_0.sft")); + storageManager.shutdown(); + + } + + @Test + void channelCount64Test() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/channelCount64.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + + assertTrue(builder.toString().contains("transactions_3.sft")); + assertTrue(builder.toString().contains("transactions_2.sft")); + assertTrue(builder.toString().contains("transactions_1.sft")); + assertTrue(builder.toString().contains("transactions_63.sft")); + storageManager.shutdown(); + + } + + @Test + void channelCount64YmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + ConfigurationLoader.New("configuration/channelCount64.yml"), + ConfigurationParserYaml.New() + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + + assertTrue(builder.toString().contains("transactions_3.sft")); + assertTrue(builder.toString().contains("transactions_2.sft")); + assertTrue(builder.toString().contains("transactions_1.sft")); + assertTrue(builder.toString().contains("transactions_63.sft")); + storageManager.shutdown(); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/ChannelDirectoryPrefixTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/ChannelDirectoryPrefixTest.java new file mode 100644 index 000000000..4f3600d9b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/ChannelDirectoryPrefixTest.java @@ -0,0 +1,143 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.hocon.types.ConfigurationParserHocon; +import org.eclipse.serializer.configuration.types.ConfigurationLoader; +import org.eclipse.serializer.configuration.yaml.types.ConfigurationParserYaml; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ChannelDirectoryPrefixTest +{ + + @TempDir + Path location; + + @Test + void channelDirectoryPrefixTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/channelDirectoryPrefix.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("channelXX_")); + + storageManager.shutdown(); + + } + + @Test + void channelDirectoryPrefixYmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + ConfigurationLoader.New("configuration/channelDirectoryPrefix.yml"), + ConfigurationParserYaml.New() + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("channelXX_")); + + storageManager.shutdown(); + + } + + @Test + void channelDirectoryPrefixHoconTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + ConfigurationLoader.New("configuration/channelDirectoryPrefix.json"), + ConfigurationParserHocon.New() + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("channelXX_")); + + storageManager.shutdown(); + + } + + @Test + void channelDirectoryPrefixXMLTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/channelDirectoryPrefix.xml" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("channelXX_")); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/ConfigurationCommonsTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/ConfigurationCommonsTest.java new file mode 100644 index 000000000..c1a8eb921 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/ConfigurationCommonsTest.java @@ -0,0 +1,63 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Duration; + +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.serializer.configuration.types.Configuration; +import org.junit.jupiter.api.Test; + +class ConfigurationCommonsTest +{ + @Test + void commons() + { + + final Integer int1 = 123; + final Double double1 = 1.23; + final String str1 = "str"; + final Boolean boolean1 = true; + final Duration duration1 = Duration.ofHours(1); + final ByteSize bytesize1 = ByteSize.New(1.23, ByteUnit.MB); + + final Configuration config = Configuration.Builder() + .set("a.b.c.int-1", int1.toString()) + .set("a.b.c.double-1", double1.toString()) + .set("a.b.string-1", str1) + .set("a.b.boolean-1", boolean1.toString()) + .set("a.duration-1", duration1.toString()) + .set("a.bytesize-1", bytesize1.toString()) + .buildConfiguration(); + + assertEquals(int1, config.getInteger("a.b.c.int-1")); + assertEquals(double1, config.getDouble("a.b.c.double-1")); + assertEquals(str1, config.get("a.b.string-1")); + assertEquals(boolean1, config.getBoolean("a.b.boolean-1")); + assertEquals(duration1, config.get("a.duration-1", Duration.class)); + assertEquals(bytesize1, config.get("a.bytesize-1", ByteSize.class)); + assertNull(config.get("not.present")); + assertFalse(config.opt("not.present").isPresent()); + + final Configuration child = config.child("a.b.c"); + assertEquals(int1, child.getInteger("int-1")); + assertEquals(double1, child.getDouble("double-1")); + assertNull(child.get("not-present")); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/Customer.java b/integration-tests/src/test/java/test/eclipse/store/configuration/Customer.java new file mode 100644 index 000000000..be9187707 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/Customer.java @@ -0,0 +1,71 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class Customer +{ + private String firstName; + private String secondName; + private String street; + private String city; + + public Customer(String firstName, String secondName, String street, String city) + { + this.firstName = firstName; + this.secondName = secondName; + this.street = street; + this.city = city; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } + + public String getStreet() + { + return street; + } + + public void setStreet(String street) + { + this.street = street; + } + + public String getCity() + { + return city; + } + + public void setCity(String city) + { + this.city = city; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/CustomerGenerator.java b/integration-tests/src/test/java/test/eclipse/store/configuration/CustomerGenerator.java new file mode 100644 index 000000000..ca8f4c504 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/CustomerGenerator.java @@ -0,0 +1,92 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class CustomerGenerator +{ + + private static final Random RANDOM = new Random(); + + private static final String[] FIRST_NAMES = { + "John", "Jane", "Michael", "Sarah", "David", "Emma", "James", "Olivia", + "Robert", "Emily", "William", "Sophia", "Richard", "Isabella", "Joseph", + "Ava", "Thomas", "Mia", "Charles", "Charlotte", "Daniel", "Amelia", + "Matthew", "Harper", "Anthony", "Evelyn", "Mark", "Abigail", "Donald", + "Elizabeth", "Paul", "Sofia", "Steven", "Avery", "Andrew", "Ella", + "Joshua", "Scarlett", "Kenneth", "Grace", "Kevin", "Chloe", "Brian", + "Victoria", "George", "Riley", "Edward", "Aria", "Ronald", "Lily" + }; + + private static final String[] LAST_NAMES = { + "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", + "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", + "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", + "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", + "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King", + "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", + "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", + "Carter", "Roberts" + }; + + private static final String[] STREET_NAMES = { + "Main Street", "High Street", "Park Avenue", "Oak Avenue", "Maple Street", + "Washington Street", "Elm Street", "Lake Street", "Hill Road", "Church Street", + "Spring Street", "Cedar Lane", "Pine Street", "River Road", "Market Street", + "Broadway", "Second Street", "Third Street", "Fourth Street", "Fifth Street", + "Sunset Boulevard", "College Street", "School Street", "Forest Drive", + "Franklin Street", "Mill Road", "Lincoln Avenue", "Madison Avenue", + "Jefferson Street", "Center Street", "Union Street", "Park Street", + "North Street", "South Street", "West Street", "East Street", "Chestnut Street", + "Walnut Street", "Bridge Street", "Cherry Street", "Willow Street", + "Maple Avenue", "Oak Drive", "Valley Road", "Highland Avenue", "Grove Street", + "Summit Avenue", "Pleasant Street", "Cambridge Road", "Oxford Street" + }; + + private static final String[] STREET_NUMBERS = { + "1", "2", "5", "10", "12", "15", "18", "20", "23", "25", "28", "30", + "33", "35", "38", "40", "42", "45", "48", "50", "55", "60", "65", "70", + "75", "80", "85", "90", "95", "100", "105", "110", "115", "120", "125", + "130", "140", "150", "160", "175", "200", "225", "250", "300", "350", + "400", "450", "500", "555", "600" + }; + + private CustomerGenerator() + { + // prevent instantiate + } + + public static Customer generateNewCustomer() + { + String firstName = FIRST_NAMES[RANDOM.nextInt(FIRST_NAMES.length)]; + String lastName = LAST_NAMES[RANDOM.nextInt(LAST_NAMES.length)]; + String streetName = STREET_NAMES[RANDOM.nextInt(STREET_NAMES.length)]; + String streetNumber = STREET_NUMBERS[RANDOM.nextInt(STREET_NUMBERS.length)]; + + return new Customer(firstName, lastName, streetName, streetNumber); + } + + public static List generateCustomers(int count) + { + List customers = new ArrayList<>(); + for (int i = 0; i < count; i++) { + customers.add(CustomerGenerator.generateNewCustomer()); + } + return customers; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/DataFilePrefixTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/DataFilePrefixTest.java new file mode 100644 index 000000000..610ba705a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/DataFilePrefixTest.java @@ -0,0 +1,115 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.types.ConfigurationLoader; +import org.eclipse.serializer.configuration.yaml.types.ConfigurationParserYaml; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class DataFilePrefixTest +{ + + @TempDir + Path location; + + @Test + void dataFilePrefixTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/dataFilePrefix.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("database_")); + + storageManager.shutdown(); + + } + + @Test + void dataFilePrefixXmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/dataFilePrefix.xml" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("database_")); + + storageManager.shutdown(); + + } + + @Test + void dataFilePrefixYmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + ConfigurationLoader.New("configuration/dataFilePrefix.yml"), + ConfigurationParserYaml.New() + ); + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("database_")); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/DataFileSuffixTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/DataFileSuffixTest.java new file mode 100644 index 000000000..43b3b9b74 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/DataFileSuffixTest.java @@ -0,0 +1,87 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class DataFileSuffixTest +{ + + @TempDir + Path location; + + @Test + void dataFileSuffixTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/dataFileSuffix.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("some_suffix")); + + storageManager.shutdown(); + + } + + @Test + void dataFileSuffixXMLTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/dataFileSuffix.xml" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("some_suffix")); + + storageManager.shutdown(); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/DeletionDirectoryTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/DeletionDirectoryTest.java new file mode 100644 index 000000000..8e727c885 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/DeletionDirectoryTest.java @@ -0,0 +1,214 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class DeletionDirectoryTest +{ + + private final Integer CUSTOMER_AMOUNT = 15; + + @Test + void deletionDirectoryTest(@TempDir Path location) + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path deleted = location.resolve("deleted"); + deleted.toFile().mkdir(); + final EmbeddedStorageManager storageManager = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(location.toString()) + .setDataFileMaximumSize(ByteSize.New(2, ByteUnit.KiB)) + .setDataFileMinimumSize(ByteSize.New(1, ByteUnit.KiB)) + .setDeletionDirectory(deleted.toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < CUSTOMER_AMOUNT; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + storageManager.issueFullFileCheck(); + + final List files = (List) FileUtils.listFiles(location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + @Test + void deleteDirectoryTest(@TempDir Path location) throws IOException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + + EmbeddedStorageManager storageManager = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(location.toString()) + .setDataFileMaximumSize(ByteSize.New(2048, ByteUnit.B)) + .setDataFileMinimumSize(ByteSize.New(1024, ByteUnit.B)) + .setDeletionDirectory(location.resolve("deleted").toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + for (int i = 0; i < 100; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + storageManager.issueFullFileCheck(); + storageManager.shutdown(); + + final List files = (List) FileUtils.listFiles(location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + } + + @Test + void deletionDirectoryIniTest(@TempDir Path location) throws IOException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path deleteLocation = location.resolve("deleted"); + deleteLocation.toFile().mkdir(); + + final Path configFilePath = location.resolve("deletionDirectory.ini"); + FileUtils.writeStringToFile(configFilePath.toFile(), "deletion-directory = " + deleteLocation.toString(), "UTF-8"); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration + .setStorageDirectory(location.toString()) + .setDataFileMaximumSize(ByteSize.New(2, ByteUnit.KiB)) + .setDataFileMinimumSize(ByteSize.New(1, ByteUnit.KiB)) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < CUSTOMER_AMOUNT; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + storageManager.issueFullFileCheck(); + + final List files = (List) FileUtils.listFiles(location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + @Test + void deletionDirectoryXMLTest(@TempDir Path location) throws IOException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path configFilePath = location.resolve("deleteDirectory.xml"); + final Path deleteLocation = location.resolve("deleted"); + deleteLocation.toFile().mkdir(); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration + .setStorageDirectory(location.toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < CUSTOMER_AMOUNT; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + storageManager.issueFullFileCheck(); + + final List files = (List) FileUtils.listFiles(location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + @Test + void deletionDirectoryXMLSimpleTest(@TempDir Path location) throws IOException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path configFilePath = location.resolve("deleteDirectory.xml"); + final Path deleteLocation = location.resolve("deleted"); + deleteLocation.toFile().mkdir(); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration + .setStorageDirectory(location.toString()) + .setDataFileMaximumSize(ByteSize.New(2, ByteUnit.KiB)) + .setDataFileMinimumSize(ByteSize.New(1, ByteUnit.KiB)) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < CUSTOMER_AMOUNT; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + storageManager.issueFullFileCheck(); + + final List files = (List) FileUtils.listFiles(location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/HouseKeepingIntervalTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/HouseKeepingIntervalTest.java new file mode 100644 index 000000000..6439f3917 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/HouseKeepingIntervalTest.java @@ -0,0 +1,115 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class HouseKeepingIntervalTest +{ + + @TempDir + Path location; + + @Test + void houseKeepingIntervalTest() throws InterruptedException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path deleteLocation = this.location.resolve("deleted"); + deleteLocation.toFile().mkdir(); + + final EmbeddedStorageManager storageManager = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(this.location.toString()) + .setDataFileMaximumSize(ByteSize.New(2, ByteUnit.KiB)) + .setDataFileMinimumSize(ByteSize.New(1, ByteUnit.KiB)) + .setDeletionDirectory(deleteLocation.toString()) + .setHousekeepingInterval(Duration.ofMillis(10)) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < 50; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + Thread.sleep(200); + + final List files = (List) FileUtils.listFiles(this.location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + @Test + void houseKeepingIntervalXMLTest() throws IOException, InterruptedException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path configFilePath = this.location.resolve("deleteDirectory.xml"); + final Path deleteLocation = this.location.resolve("deleted"); + deleteLocation.toFile().mkdir(); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration + .setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < 50; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + + final List files = (List) FileUtils.listFiles(this.location.resolve("deleted").toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/HouseKeepingTimeBudgetTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/HouseKeepingTimeBudgetTest.java new file mode 100644 index 000000000..af71da66d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/HouseKeepingTimeBudgetTest.java @@ -0,0 +1,119 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class HouseKeepingTimeBudgetTest +{ + + @TempDir + Path location; + + @TempDir + Path deleted; + + private final Integer CUSTOMER_AMOUNT = 20; + private final Integer WAIT = 20; + + @Test + void houseKeepingTimeBudgetTest() throws InterruptedException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageManager storageManager = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(this.location.toString()) + .setDataFileMaximumSize(ByteSize.New(2, ByteUnit.KiB)) + .setDataFileMinimumSize(ByteSize.New(1, ByteUnit.KiB)) + .setDeletionDirectory(deleted.toString()) + .setHousekeepingInterval(Duration.ofMillis(20)) + .setHousekeepingTimeBudget(Duration.ofMillis(10)) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < CUSTOMER_AMOUNT; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + Thread.sleep(WAIT); + + final List files = (List) FileUtils.listFiles(deleted.toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + @Test + void houseKeepingTimeBudgetXMLTest() throws IOException, InterruptedException + { + Customer customer = CustomerGenerator.generateNewCustomer(); + + final Path configFilePath = this.location.resolve("deleteDirectory.xml"); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration + .setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(customer) + .start(); + + for (int i = 0; i < CUSTOMER_AMOUNT; i++) { + customer = CustomerGenerator.generateNewCustomer(); + storageManager.store(customer); + } + + Thread.sleep(WAIT); + + final List files = (List) FileUtils.listFiles(deleted.toFile(), null, true); + assertTrue(files.size() > 0, files.toString()); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/StorageDirectoryTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/StorageDirectoryTest.java new file mode 100644 index 000000000..1cbbb299f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/StorageDirectoryTest.java @@ -0,0 +1,115 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.io.XIO; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class StorageDirectoryTest +{ + + @TempDir + Path location; + + Path configFilePath; + + + @Test + void storageDirectoryTest() throws IOException + { + this.configFilePath = this.location.resolve("storage-directory.ini"); + FileUtils.writeStringToFile(this.configFilePath.toFile(), "storage-directory = " + this.location.toString(), "UTF-8"); + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + assertTrue(files.size() > 2, files.toString()); + + storageManager.shutdown(); + } + + @Test + void storageDirectoryXMLTest() throws IOException + { + + this.configFilePath = this.location.resolve("storage-directory.xml"); + + final String xmlConfig = + "\n" + + "\n" + + "\t\n" + + ""; + + FileUtils.writeStringToFile(this.configFilePath.toFile(), xmlConfig, "UTF-8"); + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + final EmbeddedStorageManager storageManager = configuration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + assertTrue(files.size() > 2); + + storageManager.shutdown(); + } + + + @Test + void storageDirectoryHomeTest() throws IOException + { + this.configFilePath = this.location.resolve("baseDirectory.xml"); + + final String xmlConfig = + "\n" + + "\n" + + "\t\n" + + ""; + + FileUtils.writeStringToFile(this.configFilePath.toFile(), xmlConfig, "UTF-8"); + + final ADirectory baseDirectory = EmbeddedStorageConfiguration.load(this.configFilePath.toString()) + .createEmbeddedStorageFoundation() + .getConfiguration() + .fileProvider() + .baseDirectory(); + + final Path basePath = XIO.Path(baseDirectory.toPath()); + final Path userHomePath = Paths.get(System.getProperty("user.home")); + assertTrue(basePath.startsWith(userHomePath)); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFilePrefixTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFilePrefixTest.java new file mode 100644 index 000000000..e5ba237b5 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFilePrefixTest.java @@ -0,0 +1,88 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TransactionFilePrefixTest +{ + + @TempDir + Path location; + + @Test + void transactionFilePrefixTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/transactionFilePrefix.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("supersupertransacation_0.sft")); + + storageManager.shutdown(); + + } + + @Test + void transactionFilePrefixXmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/transactionFilePrefix.xml" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("supersupertransacation_0.sft")); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFileSizeTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFileSizeTest.java new file mode 100644 index 000000000..83bc7a19b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFileSizeTest.java @@ -0,0 +1,93 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.file.Path; + +import org.eclipse.serializer.configuration.exceptions.ConfigurationException; +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.datafaker.Faker; + +public class TransactionFileSizeTest +{ + + @TempDir + Path location; + + private Faker faker = new Faker(); + + //@Test + @Test + void setSizeConfigTest() throws InterruptedException + { + EmbeddedStorageFoundation embeddedStorageFoundation = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(location.toAbsolutePath() + .toString()) + .setTransactionFileMaximumSize(ByteSize.New("1024")) + .createEmbeddedStorageFoundation(); + + Customer customer = new Customer("first"); + try (EmbeddedStorageManager storageManager = embeddedStorageFoundation.start(customer)) { + for (int i = 0; i < 500; i++) { + customer.setName(faker + .name() + .lastName()); + storageManager.store(customer); + } + } + File file = Path.of(location.toAbsolutePath() + .toString(), "channel_0", "transactions_0.sft") + .toFile(); + long length = file.length(); + //System.out.println(length); + assertTrue(length < 15_000); + } + + @Test + void setBadConfigTest() + { + assertThrows(ConfigurationException.class, () -> EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(location.toAbsolutePath() + .toString()) + .setTransactionFileMaximumSize(ByteSize.New("50")) + .createEmbeddedStorageFoundation()); + } + + static class Customer + { + String name; + + public Customer(String name) + { + this.name = name; + } + + public void setName(String name) + { + this.name = name; + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFileSuffixTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFileSuffixTest.java new file mode 100644 index 000000000..3a9e5a87f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/TransactionFileSuffixTest.java @@ -0,0 +1,86 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TransactionFileSuffixTest +{ + + @TempDir + Path location; + + @Test + void transactionFileSuffixTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/transactionFileSuffix.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("_0.suffix_transaction")); + + storageManager.shutdown(); + + } + + @Test + void transactionFileSuffixXmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/transactionFileSuffix.xml" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("_0.suffix_transaction")); + + storageManager.shutdown(); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/TruncationDirectoryTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/TruncationDirectoryTest.java new file mode 100644 index 000000000..7cc6aca29 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/TruncationDirectoryTest.java @@ -0,0 +1,169 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TruncationDirectoryTest +{ + + @TempDir + Path location; + + Path configFilePath; + + private final String TRANSACTION_FILENAME = "transactions_1.sft"; + + @Test + void truncationDirectoryTest() throws IOException + { + this.configFilePath = this.location.resolve("truncationDirectory.ini"); + final Path truncationPath = this.location.resolve("trunc"); + truncationPath.toFile().mkdir(); + //FileUtils.writeStringToFile(configFilePath.toFile(),"truncationDirectory = "+ truncationPath.toString()+"\nchannelCount = 2", "UTF-8"); + FileUtils.writeStringToFile(this.configFilePath.toFile(), "channel-count = 2", "UTF-8"); + + List customers = CustomerGenerator.generateCustomers(100); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + final Path backupTransaction; + try (EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .setTruncationDirectory(truncationPath.toString()).createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start()) { + + } + // copy trans file + backupTransaction = this.location.resolve("trans-backup"); + FileUtils.copyFile(this.location.resolve("channel_1/" + this.TRANSACTION_FILENAME).toFile(), backupTransaction.resolve(this.TRANSACTION_FILENAME).toFile()); + + try (EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .setTruncationDirectory(truncationPath.toString()).createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start()) { + // add one Customer + customers.add(CustomerGenerator.generateNewCustomer()); + storageManager.store(customers); + } + + //change file content + byte[] data = IOUtils.toByteArray(this.location.resolve("channel_1/channel_1_1.dat").toUri()); + data = this.removeLastBytes(data, 4); + FileUtils.writeByteArrayToFile(this.location.resolve("channel_1/channel_1_1.dat").toFile(), data); + + + // replace old transaction file + FileUtils.copyFile(backupTransaction.resolve(this.TRANSACTION_FILENAME).toFile(), this.location.resolve("channel_1/" + this.TRANSACTION_FILENAME).toFile()); + + //load again + customers = new ArrayList<>(); + + try (EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start()) { + final List files = (List) FileUtils.listFiles(truncationPath.toFile(), null, true); + assertTrue(files.size() > 1, files.toString()); + } + } + + @Test + void truncationDirectoryXMLTest() throws IOException, InterruptedException + { + this.configFilePath = this.location.resolve("truncationDirectory.xml"); + final Path truncationPath = this.location.resolve("trunc"); + truncationPath.toFile().mkdir(); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(this.configFilePath.toFile(), builder.toString(), "UTF-8"); + + List customers = CustomerGenerator.generateCustomers(100); + + final EmbeddedStorageConfigurationBuilder configuration = + EmbeddedStorageConfiguration.load(this.configFilePath.toString()); + + try (EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start()) { + + } + + //printFiles(this.location.toFile()); + + final Path backupTransaction; + try (EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start()) { + + // copy trans file + backupTransaction = this.location.resolve("trans-backup"); + FileUtils.copyFile(this.location.resolve("channel_1/" + this.TRANSACTION_FILENAME).toFile(), backupTransaction.resolve(this.TRANSACTION_FILENAME).toFile()); + + // add one Customer ok + customers.add(CustomerGenerator.generateNewCustomer()); + storageManager.store(customers); + } + // + + //change file content + byte[] data = IOUtils.toByteArray(this.location.resolve("channel_1/channel_1_1.dat").toUri()); + data = this.removeLastBytes(data, 4); + FileUtils.writeByteArrayToFile(this.location.resolve("channel_1/channel_1_1.dat").toFile(), data); + + // replace old transaction file + FileUtils.copyFile(backupTransaction.resolve(this.TRANSACTION_FILENAME).toFile(), this.location.resolve("channel_1/" + this.TRANSACTION_FILENAME).toFile()); + + //load again + customers = new ArrayList<>(); + + try (EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customers).start()) { + final List files = (List) FileUtils.listFiles(truncationPath.toFile(), null, true); + assertTrue(files.size() > 1, files.toString()); + } + } + + private void printFiles(File dir) + { + Collection files = FileUtils.listFiles(dir, null, true); + files.forEach(System.out::println); + } + + private byte[] removeLastBytes(byte[] data, final int numberOfBytesToRemove) + { + final int size = data.length; + for (int i = size; i > (size - numberOfBytesToRemove); i--) { + data = ArrayUtils.remove(data, i - 1); + } + return data; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/TypeDictionaryFilenameTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/TypeDictionaryFilenameTest.java new file mode 100644 index 000000000..6c074726c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/TypeDictionaryFilenameTest.java @@ -0,0 +1,88 @@ +package test.eclipse.store.configuration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TypeDictionaryFilenameTest +{ + + @TempDir + Path location; + + @Test + void transactionFileSuffixTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/typeDictionaryFilename.ini" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("somesupertypeDictionaryFilename.some_suffix")); + + storageManager.shutdown(); + + } + + @Test + void transactionFileSuffixXmlTest() throws IOException + { + + final Customer customer = CustomerGenerator.generateNewCustomer(); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load( + "configuration/typeDictionaryFilename.xml" + ); + + final EmbeddedStorageManager storageManager = configuration.setStorageDirectory(this.location.toString()) + .createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + final List files = (List) FileUtils.listFiles(this.location.toFile(), null, true); + + final StringBuilder builder = new StringBuilder(); + for (final File file : files) { + builder.append(file.getCanonicalPath()); + } + assertTrue(builder.toString().contains("somesupertypeDictionaryFilename.some_suffix")); + + storageManager.shutdown(); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/convert/ByteSizeTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/convert/ByteSizeTest.java new file mode 100644 index 000000000..b29beed7d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/convert/ByteSizeTest.java @@ -0,0 +1,52 @@ +package test.eclipse.store.configuration.convert; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.junit.jupiter.api.Test; + + +class ByteSizeTest +{ + + @Test + void parser() + { + final ByteSize size = ByteSize.New(1.23, ByteUnit.MB); + assertEquals(size, ByteSize.New(size.toString())); + } + + @Test + void equality() + { + assertEquals( + ByteSize.New(0.5, ByteUnit.MB), + ByteSize.New(500, ByteUnit.KB) + ); + } + + @Test + void error() + { + assertThrows( + IllegalArgumentException.class, + () -> ByteSize.New("1xyz") + ); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/convert/ByteUnitTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/convert/ByteUnitTest.java new file mode 100644 index 000000000..8237d8ab7 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/convert/ByteUnitTest.java @@ -0,0 +1,40 @@ +package test.eclipse.store.configuration.convert; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.junit.jupiter.api.Test; + + +class ByteUnitTest +{ + + @Test + void mbToGg() + { + final Double d = ByteUnit.convert(1.5, ByteUnit.MB).to(ByteUnit.GB); + assertEquals(0.0015, d); + } + + @Test + void findUnitName() + { + final ByteUnit b = ByteUnit.ofName("kibibyte"); + //System.out.println(b.toString()); + assertEquals("KiB", b.toString()); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/exception/ChannelCountValidatorTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/exception/ChannelCountValidatorTest.java new file mode 100644 index 000000000..0efac195b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/exception/ChannelCountValidatorTest.java @@ -0,0 +1,73 @@ +package test.eclipse.store.configuration.exception; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.exceptions.StorageExceptionStructureValidation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.configuration.Customer; +import test.eclipse.store.configuration.CustomerGenerator; + +public class ChannelCountValidatorTest +{ + + @TempDir + Path location; + + @Test + public void channelCountDowngradeValidatorTest() + { + final Customer customer = CustomerGenerator.generateNewCustomer(); + + EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfigurationBuilder.New(); + configuration.setChannelCount(4); + configuration.setStorageDirectory(location.toAbsolutePath().toString()); + configuration.buildConfiguration(); + + EmbeddedStorageManager embeddedStorageManager = configuration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer).start(); + + embeddedStorageManager.shutdown(); + + Assertions.assertThrows(StorageExceptionStructureValidation.class, () -> EmbeddedStorage.start(customer, location)); + } + + @Test + public void channelCountUpgradeValidatorTest() + { + final Customer customer = CustomerGenerator.generateNewCustomer(); + + EmbeddedStorageManager start = EmbeddedStorage.start(customer, location); + start.shutdown(); + + EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfigurationBuilder.New(); + configuration.setChannelCount(4); + configuration.setStorageDirectory(location.toAbsolutePath().toString()); + configuration.buildConfiguration(); + + EmbeddedStorageManager embeddedStorageManager = configuration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(customer); + Assertions.assertThrows(StorageExceptionStructureValidation.class, embeddedStorageManager::start); + + Assertions.assertFalse(embeddedStorageManager.isRunning()); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/exception/DurationBadFormatTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/exception/DurationBadFormatTest.java new file mode 100644 index 000000000..6ec5aaa99 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/exception/DurationBadFormatTest.java @@ -0,0 +1,82 @@ +package test.eclipse.store.configuration.exception; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.exceptions.ConfigurationException; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class DurationBadFormatTest +{ + + @TempDir + Path location; + + @Test + public void badFormatWithUnit() throws IOException + { + + final Path configFilePath = this.location.resolve("deleteDirectory.xml"); + final Path deleteLocation = this.location.resolve("deleted"); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + assertThrows(ConfigurationException.class, () -> { + EmbeddedStorageConfiguration.load(configFilePath.toString()) + .createEmbeddedStorageFoundation(); + }); + } + + @Test + public void badFormatWithoutUnit() throws IOException + { + + final Path configFilePath = this.location.resolve("deleteDirectory.xml"); + final Path deleteLocation = this.location.resolve("deleted"); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + assertThrows(ConfigurationException.class, () -> { + EmbeddedStorageConfiguration.load(configFilePath.toString()) + .createEmbeddedStorageFoundation(); + }); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/configuration/exception/InvalidStorageConfigurationExceptionTest.java b/integration-tests/src/test/java/test/eclipse/store/configuration/exception/InvalidStorageConfigurationExceptionTest.java new file mode 100644 index 000000000..eb118283b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/configuration/exception/InvalidStorageConfigurationExceptionTest.java @@ -0,0 +1,104 @@ +package test.eclipse.store.configuration.exception; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.configuration.exceptions.ConfigurationException; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class InvalidStorageConfigurationExceptionTest +{ + + @TempDir + Path location; + + @Test + void invalidStorageConfigurationException() throws IOException, InterruptedException + { + + final Path configFilePath = this.location.resolve("deleteDirectory.xml"); + final Path deleteLocation = this.location.resolve("deleted"); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + assertThrows(ConfigurationException.class, () -> { + EmbeddedStorageConfiguration.load(configFilePath.toString()) + .createEmbeddedStorageFoundation(); + }); + + } + + @Test + void maxStorageSizeExceedException() throws IOException, InterruptedException + { + + final Path configFilePath = this.location.resolve("deleteDirectory.xml"); + final Path deleteLocation = this.location.resolve("deleted"); + + final StringBuilder builder = new StringBuilder(); + builder.append("\n"); + builder.append("\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append("\t\n"); + builder.append(""); + + FileUtils.writeStringToFile(configFilePath.toFile(), builder.toString(), "UTF-8"); + + assertThrows(ConfigurationException.class, () -> { + EmbeddedStorageConfiguration.load(configFilePath.toString()) + .createEmbeddedStorageFoundation(); + }); + } + + @Test + void channelCountBadNumberTest() throws IOException + { + final Path configFilePath = this.location.resolve("channelCount.xml"); + + final String xmlConfig = + "\n" + + "\n" + + "\t\n" + + ""; + + FileUtils.writeStringToFile(configFilePath.toFile(), xmlConfig, "UTF-8"); + + assertThrows(ConfigurationException.class, () -> { + EmbeddedStorageConfiguration.load(configFilePath.toString()) + .createEmbeddedStorageFoundation(); + }); + + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/conversion/ConversionTest.java b/integration-tests/src/test/java/test/eclipse/store/conversion/ConversionTest.java new file mode 100644 index 000000000..a08e67fc1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/conversion/ConversionTest.java @@ -0,0 +1,124 @@ +package test.eclipse.store.conversion; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfiguration; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.tools.storage.converter.MainUtilStorageConverter; +import org.eclipse.store.storage.embedded.tools.storage.converter.StorageConverter; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.datafaker.Faker; + +public class ConversionTest +{ + + @TempDir + Path src; + + @TempDir + Path dest; + + final private Faker faker = new Faker(); + + @Test + public void conversionTest() + { + + List dataSrc = new ArrayList<>(); + List dataDst = new ArrayList<>(); + + for (int i = 0; i < 1000; i++) { + dataSrc.add(faker.lorem().sentence()); + } + + final EmbeddedStorageFoundation sourceFoundation = EmbeddedStorageConfiguration.Builder() + .setStorageDirectory(src.toAbsolutePath().toString()) + .setChannelCount(16) + .createEmbeddedStorageFoundation(); + + final EmbeddedStorageFoundation targetFoundation = EmbeddedStorageConfiguration.Builder() + .setStorageDirectory(dest.toAbsolutePath().toString()) + .setChannelCount(1) + .createEmbeddedStorageFoundation(); + + + EmbeddedStorageManager sourceStorage = sourceFoundation.start(dataSrc); + sourceStorage.shutdown(); + + + final StorageConverter storageConverter = new StorageConverter(sourceFoundation.getConfiguration(), targetFoundation.getConfiguration()); + storageConverter.start(); + + EmbeddedStorageManager dstStorage = targetFoundation.start(dataDst); + dstStorage.shutdown(); + + Assertions.assertEquals(dataSrc.size(), dataDst.size()); + + //System.out.println("Storage conversion finished!"); + + + } + + @Test + public void conversionMainTest(@TempDir Path configFolder) throws IOException + { + + List dataSrc = new ArrayList<>(); + List dataDst = new ArrayList<>(); + + for (int i = 0; i < 1000; i++) { + dataSrc.add(faker.lorem().sentence()); + } + + Path srcConfig = configFolder.resolve("scr-config.ini"); + String srcConfigContent = "channel-count = 2\n" + + "storage-directory = " + src.toAbsolutePath(); + FileUtils.writeStringToFile(srcConfig.toFile(), srcConfigContent, "UTF-8"); + + + Path dstConfig = configFolder.resolve("dst-config.ini"); + String dstConfigContent = "channel-count = 16\n" + + "storage-directory = " + dest.toAbsolutePath(); + FileUtils.writeStringToFile(dstConfig.toFile(), dstConfigContent, "UTF-8"); + + final EmbeddedStorageConfigurationBuilder configuration = EmbeddedStorageConfiguration.load(srcConfig.toString()); + final EmbeddedStorageManager srcStorage = configuration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(dataSrc).start(); + srcStorage.shutdown(); + + + MainUtilStorageConverter.main(new String[]{String.valueOf(srcConfig.toAbsolutePath()), String.valueOf(dstConfig.toAbsolutePath())}); + + final EmbeddedStorageConfigurationBuilder dstConfiguration = EmbeddedStorageConfiguration.load(dstConfig.toString()); + final EmbeddedStorageManager dstStorage = dstConfiguration.createEmbeddedStorageFoundation().createEmbeddedStorageManager(dataDst).start(); + dstStorage.shutdown(); + + Assertions.assertEquals(dataSrc.size(), dataDst.size()); + + //System.out.println("Storage conversion finished!"); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/customtypehandler/CustomBufferedImageHandler.java b/integration-tests/src/test/java/test/eclipse/store/customtypehandler/CustomBufferedImageHandler.java new file mode 100644 index 000000000..13b4055c2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/customtypehandler/CustomBufferedImageHandler.java @@ -0,0 +1,94 @@ +package test.eclipse.store.customtypehandler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.eclipse.serializer.persistence.binary.types.AbstractBinaryHandlerCustomValue; +import org.eclipse.serializer.persistence.binary.types.Binary; +import org.eclipse.serializer.persistence.types.PersistenceLoadHandler; +import org.eclipse.serializer.persistence.types.PersistenceStoreHandler; + + +public class CustomBufferedImageHandler extends AbstractBinaryHandlerCustomValue +{ + + static boolean stored = false; // just to check, if the handler was called + + public CustomBufferedImageHandler() + { + super(BufferedImage.class, CustomFields(CustomField(long.class, "capacity"), bytes("value"))); + } + + @Override + public boolean hasVaryingPersistedLengthInstances() + { + return false; + } + + @Override + public void store(final Binary bytes, final BufferedImage instance, final long objectId, final PersistenceStoreHandler handler) + { + stored = true; // just to check, if the handler was called + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (ImageOutputStream ios = new MemoryCacheImageOutputStream(bos)) { + ImageIO.write(instance, "png", ios); + } catch (final IOException e) { + throw new RuntimeException(e); + } + + bytes.store_bytes(this.typeId(), objectId, bos.toByteArray()); + } + + @Override + public BufferedImage create(final Binary bytes, final PersistenceLoadHandler handler) + { + final byte[] blob = bytes.build_bytes(); + + BufferedImage image; + + try (ByteArrayInputStream bis = new ByteArrayInputStream(blob)) { + image = ImageIO.read(bis); + } catch (final IOException e) { + throw new RuntimeException(e); + } + + return image; + } + + @Override + public void validateState(final Binary data, final BufferedImage instance, final PersistenceLoadHandler handler) + { + } + + @Override + public BufferedImage getValidationStateFromInstance(BufferedImage bufferedImage) + { + return bufferedImage; + } + + @Override + public BufferedImage getValidationStateFromBinary(Binary binary) + { + //TODO fix temporal - + return null; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/customtypehandler/CustomBufferedImageHandlerTest.java b/integration-tests/src/test/java/test/eclipse/store/customtypehandler/CustomBufferedImageHandlerTest.java new file mode 100644 index 000000000..8262dab2b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/customtypehandler/CustomBufferedImageHandlerTest.java @@ -0,0 +1,76 @@ +package test.eclipse.store.customtypehandler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.awt.image.BufferedImage; +import java.nio.file.Path; + +import org.eclipse.serializer.persistence.binary.jdk8.types.BinaryHandlersJDK8; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class CustomBufferedImageHandlerTest +{ + + @TempDir + Path location; + + private MyRoot myRoot = new MyRoot(); + + @Test + void customBufferedImageHandlerTest() + { + try (EmbeddedStorageManager storage = EmbeddedStorage + .Foundation(location) + .onConnectionFoundation(f -> f.registerCustomTypeHandlers(new CustomBufferedImageHandler())) + .start(myRoot) + ) { + // no op + } + } + + //https://github.com/eclipse-store/store/issues/204 + @Test + void initializationTest() + { + EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(location); + + try (EmbeddedStorageManager storage = foundation + .onConnectionFoundation(BinaryHandlersJDK8::registerJDK8TypeHandlers) + .onConnectionFoundation(f -> f.registerCustomTypeHandlers(new CustomBufferedImageHandler())) + .start(myRoot)) { + // no op + + assertTrue(CustomBufferedImageHandler.stored); + + } + + } + + public static class MyRoot + { + BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + + public BufferedImage getImage() + { + return image; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/deepcopy/DeepCopyLazyTest.java b/integration-tests/src/test/java/test/eclipse/store/deepcopy/DeepCopyLazyTest.java new file mode 100644 index 000000000..736e23632 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/deepcopy/DeepCopyLazyTest.java @@ -0,0 +1,474 @@ +package test.eclipse.store.deepcopy; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.serializer.ObjectCopier; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Deep-copy tests focused on Lazy references: loaded, cleared, null, + * shared, nested, circular, inside collections. Goal is to surface + * defects, not produce a green run. + */ + +@Disabled("https://github.com/microstream-one/internal/issues/53") +public class DeepCopyLazyTest +{ + + EmbeddedStorageManager storageManager; + + @TempDir + Path tempDir; + + @AfterEach + public void afterTest() + { + if (storageManager != null) { + storageManager.shutdown(); + } + } + + @Test + public void loadedLazy_subjectIsDeepCopied() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Alice", 30)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + assertNotSame(root, copy); + assertNotNull(copy.lazyPerson); + assertNotSame(root.lazyPerson, copy.lazyPerson); + assertNotSame(root.lazyPerson.get(), copy.lazyPerson.get()); + assertEquals("Alice", copy.lazyPerson.get().name); + assertEquals(30, copy.lazyPerson.get().age); + } + + @Test + public void loadedLazy_wrapperTypeIsPreserved() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Bob", 40)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + // The Lazy wrapper must survive — it must not be silently + // unwrapped to its subject during deep copy. + assertTrue(copy.lazyPerson instanceof Lazy); + } + + @Test + public void loadedLazy_mutationDoesNotLeakBack() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Carol", 25)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + copy.lazyPerson.get().name = "MUTATED"; + copy.lazyPerson.get().age = 999; + + assertEquals("Carol", root.lazyPerson.get().name); + assertEquals(25, root.lazyPerson.get().age); + } + + @Test + public void loadedLazy_originalIsNotForceClearedByCopy() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Dave", 50)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + objectCopier.copy(root); + + // Deep copy must not clear/unload the source graph as a side effect. + assertNotNull(root.lazyPerson.peek()); + assertTrue(root.lazyPerson.isLoaded()); + } + + @Test + public void lazyOfNull_deepCopy() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(null); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + // Lazy.Reference(null) must round-trip as a Lazy wrapping null, + // not be collapsed to a plain null field on the copy. + assertNotNull(copy.lazyPerson); + assertNull(copy.lazyPerson.peek()); + assertNull(copy.lazyPerson.get()); + } + + @Test + public void sharedLazyWrapper_identityIsPreserved() + { + Lazy shared = Lazy.Reference(new Person("Eve", 28)); + TwoLazyHolder root = new TwoLazyHolder(); + root.left = shared; + root.right = shared; + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + TwoLazyHolder copy = objectCopier.copy(root); + + // Two fields pointing at the same Lazy instance must remain + // a single shared instance after deep copy. + assertSame(copy.left, copy.right); + } + + @Test + public void sharedLazySubject_identityIsPreserved() + { + Person samePerson = new Person("Frank", 60); + TwoLazyHolder root = new TwoLazyHolder(); + root.left = Lazy.Reference(samePerson); + root.right = Lazy.Reference(samePerson); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + TwoLazyHolder copy = objectCopier.copy(root); + + assertNotSame(copy.left, copy.right); + // Two distinct Lazy wrappers that referenced the same subject + // must, in the copy, still resolve to one and the same subject. + assertSame(copy.left.get(), copy.right.get()); + } + + @Test + public void copiedLazy_isNotStored() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Greta", 33)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + // ObjectCopier never persists the result to a real storage. + assertFalse(copy.lazyPerson.isStored()); + } + + @Test + public void copiedLazy_isLoaded() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Hugo", 41)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + assertTrue(copy.lazyPerson.isLoaded()); + } + + @Test + public void copiedLazy_clearThrows_butForceClearWorks() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Ivan", 22)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder copy = objectCopier.copy(root); + + // Per Lazy contract: clear() requires isStored(), forceClear() does not. + assertThrows(IllegalStateException.class, () -> copy.lazyPerson.clear()); + assertDoesNotThrow(() -> copy.lazyPerson.forceClear()); + assertNull(copy.lazyPerson.peek()); + } + + @Test + public void lazyInsideList_deepCopy() + { + List> list = new ArrayList<>(); + list.add(Lazy.Reference(new Person("L1", 1))); + list.add(Lazy.Reference(new Person("L2", 2))); + list.add(Lazy.Reference(new Person("L3", 3))); + + storageManager = EmbeddedStorage.start(list, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + List> copy = objectCopier.copy(list); + + assertEquals(3, copy.size()); + for (int i = 0; i < list.size(); i++) { + assertNotSame(list.get(i), copy.get(i)); + assertNotSame(list.get(i).get(), copy.get(i).get()); + assertEquals(list.get(i).get().name, copy.get(i).get().name); + } + } + + @Test + public void lazyInsideMap_deepCopy() + { + Map> map = new HashMap<>(); + map.put("a", Lazy.Reference(new Person("A", 10))); + map.put("b", Lazy.Reference(new Person("B", 20))); + + storageManager = EmbeddedStorage.start(map, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Map> copy = objectCopier.copy(map); + + assertEquals(2, copy.size()); + assertEquals("A", copy.get("a").get().name); + assertEquals("B", copy.get("b").get().name); + assertNotSame(map.get("a"), copy.get("a")); + assertNotSame(map.get("a").get(), copy.get("a").get()); + } + + @Test + public void lazyContainsCollection_deepCopy() + { + List people = new ArrayList<>(); + people.add(new Person("X", 1)); + people.add(new Person("Y", 2)); + + CollectionHolder root = new CollectionHolder(); + root.lazyList = Lazy.Reference(people); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + CollectionHolder copy = objectCopier.copy(root); + + assertNotSame(root.lazyList, copy.lazyList); + assertNotSame(root.lazyList.get(), copy.lazyList.get()); + assertEquals(2, copy.lazyList.get().size()); + + copy.lazyList.get().add(new Person("Z", 3)); + assertEquals(2, root.lazyList.get().size()); + } + + @Test + public void nestedLazy_deepCopy() + { + Holder inner = new Holder("inner"); + inner.lazyPerson = Lazy.Reference(new Person("Nested", 7)); + + NestedHolder root = new NestedHolder(); + root.lazyHolder = Lazy.Reference(inner); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + NestedHolder copy = objectCopier.copy(root); + + assertNotSame(root.lazyHolder, copy.lazyHolder); + assertNotSame(root.lazyHolder.get(), copy.lazyHolder.get()); + assertNotSame(root.lazyHolder.get().lazyPerson, copy.lazyHolder.get().lazyPerson); + assertEquals("Nested", copy.lazyHolder.get().lazyPerson.get().name); + + copy.lazyHolder.get().lazyPerson.get().name = "MUTATED"; + assertEquals("Nested", root.lazyHolder.get().lazyPerson.get().name); + } + + @Test + public void circularGraphThroughLazy_deepCopy() + { + CircularNode a = new CircularNode("A"); + CircularNode b = new CircularNode("B"); + a.next = Lazy.Reference(b); + b.next = Lazy.Reference(a); + + storageManager = EmbeddedStorage.start(a, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + CircularNode copy = objectCopier.copy(a); + + assertEquals("A", copy.name); + assertEquals("B", copy.next.get().name); + // The cycle must round-trip as a real cycle, not as a duplicated A. + assertSame(copy, copy.next.get().next.get()); + } + + @Test + public void sameLazyCopiedTwice_copiesAreIndependent() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Multi", 99)); + + storageManager = EmbeddedStorage.start(root, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + Holder c1 = objectCopier.copy(root); + Holder c2 = objectCopier.copy(root); + + assertNotSame(c1.lazyPerson, c2.lazyPerson); + assertNotSame(c1.lazyPerson.get(), c2.lazyPerson.get()); + + c1.lazyPerson.get().name = "C1"; + c2.lazyPerson.get().name = "C2"; + assertEquals("Multi", root.lazyPerson.get().name); + } + + @Test + public void unloadedLazy_deepCopy_subjectIsRecovered() + { + // Bug hunt: a Lazy that is stored and then cleared has its subject + // only on disk — ObjectCopier creates its own PersistenceManager + // and does not share the storage's loader, so this is exactly the + // place where data may silently be lost. + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Storage-resident", 77)); + + storageManager = EmbeddedStorage.start(root, tempDir); + + assertTrue(root.lazyPerson.isStored()); + root.lazyPerson.clear(); + assertNull(root.lazyPerson.peek()); + assertFalse(root.lazyPerson.isLoaded()); + + ObjectCopier objectCopier = ObjectCopier.New(); + Holder copy = objectCopier.copy(root); + + assertNotNull(copy.lazyPerson); + Person recovered = copy.lazyPerson.get(); + assertNotNull(recovered); + assertEquals("Storage-resident", recovered.name); + assertEquals(77, recovered.age); + } + + @Test + public void unloadedLazy_deepCopy_originalRemainsCleared() + { + Holder root = new Holder("root"); + root.lazyPerson = Lazy.Reference(new Person("Stays-cleared", 55)); + + storageManager = EmbeddedStorage.start(root, tempDir); + root.lazyPerson.clear(); + + ObjectCopier objectCopier = ObjectCopier.New(); + objectCopier.copy(root); + + // Deep copy must not silently re-populate the original cleared Lazy. + assertNull(root.lazyPerson.peek()); + } + + @Test + public void unloadedLazy_insideContainer_deepCopy() + { + // Mixed state inside a single graph: some lazies loaded, one cleared. + // All must round-trip without losing the cleared subject. + List> list = new ArrayList<>(); + list.add(Lazy.Reference(new Person("Loaded-1", 1))); + list.add(Lazy.Reference(new Person("Cleared-2", 2))); + list.add(Lazy.Reference(new Person("Loaded-3", 3))); + + storageManager = EmbeddedStorage.start(list, tempDir); + list.get(1).clear(); + assertNull(list.get(1).peek()); + + ObjectCopier objectCopier = ObjectCopier.New(); + List> copy = objectCopier.copy(list); + + assertEquals(3, copy.size()); + assertEquals("Loaded-1", copy.get(0).get().name); + Person mid = copy.get(1).get(); + assertNotNull(mid); + assertEquals("Cleared-2", mid.name); + assertEquals("Loaded-3", copy.get(2).get().name); + } + + static class Person + { + String name; + int age; + Person friend; + + Person(String name, int age) + { + this.name = name; + this.age = age; + } + } + + static class Holder + { + String label; + Lazy lazyPerson; + + Holder() + { + } + + Holder(String label) + { + this.label = label; + } + } + + static class TwoLazyHolder + { + Lazy left; + Lazy right; + } + + static class NestedHolder + { + Lazy lazyHolder; + } + + static class CollectionHolder + { + Lazy> lazyList; + } + + static class CircularNode + { + String name; + Lazy next; + + CircularNode(String name) + { + this.name = name; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/deepcopy/DeepCopyTest.java b/integration-tests/src/test/java/test/eclipse/store/deepcopy/DeepCopyTest.java new file mode 100644 index 000000000..74b9ee49f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/deepcopy/DeepCopyTest.java @@ -0,0 +1,544 @@ +package test.eclipse.store.deepcopy; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.serializer.ObjectCopier; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +public class DeepCopyTest +{ + + EmbeddedStorageManager storageManager; + + @TempDir + Path tempDir; + + @Test + public void deepCopyTest() + { + + List children = new ArrayList<>(); + + DeepPerson father = new DeepPerson("Alex", "Samohoj", 190, true); + DeepPerson mother = new DeepPerson("Alexandra", "Vesela", 160, false); + DeepPerson son = new DeepPerson("Sohn", "Samohoj", 60, true, father, mother); + children.add(son); + + storageManager = EmbeddedStorage.start(children, tempDir); + + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson son2 = objectCopier.copy(son); + + DeepPerson anotherMother = new DeepPerson("Maria", "Veryclever", 170, false); + son2.setMather(anotherMother); + + assertNotEquals(son.getMather(), son2.getMather()); + + children.add(son2); + + assertNotEquals(children.get(0).getMather(), children.get(1).getMather()); + + } + + @Test + public void deepCopyWithNullParentsTest() + { + List people = new ArrayList<>(); + + // Person with null parents + DeepPerson orphan = new DeepPerson("Orphan", "Child", 120, true, null, null); + people.add(orphan); + + storageManager = EmbeddedStorage.start(people, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson orphanCopy = objectCopier.copy(orphan); + + assertNotSame(orphan, orphanCopy); + assertEquals(orphan.getFirstName(), orphanCopy.getFirstName()); + assertNull(orphanCopy.getFather()); + assertNull(orphanCopy.getMather()); + } + + @Test + public void deepCopyCircularReferenceTest() + { + List people = new ArrayList<>(); + + DeepPerson person1 = new DeepPerson("Person1", "First", 180, true); + DeepPerson person2 = new DeepPerson("Person2", "Second", 170, false); + + // Create circular reference + person1.setFather(person2); + person2.setMather(person1); + + people.add(person1); + + storageManager = EmbeddedStorage.start(people, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson person1Copy = objectCopier.copy(person1); + + assertNotSame(person1, person1Copy); + assertNotSame(person1.getFather(), person1Copy.getFather()); + assertNotNull(person1Copy.getFather()); + assertNotNull(person1Copy.getFather().getMather()); + } + + @Test + public void deepCopyEmptyPersonTest() + { + List people = new ArrayList<>(); + + DeepPerson emptyPerson = new DeepPerson(null, null, null, null); + people.add(emptyPerson); + + storageManager = EmbeddedStorage.start(people, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson copy = objectCopier.copy(emptyPerson); + + assertNotSame(emptyPerson, copy); + assertNull(copy.getFirstName()); + assertNull(copy.getSecondName()); + assertNull(copy.getHigh()); + assertNull(copy.getMan()); + } + + @Test + public void deepCopyComplexFamilyTreeTest() + { + List family = new ArrayList<>(); + + // Grandparents + DeepPerson grandpa = new DeepPerson("Grandpa", "Smith", 170, true); + DeepPerson grandma = new DeepPerson("Grandma", "Smith", 160, false); + + // Parents + DeepPerson father = new DeepPerson("Father", "Smith", 180, true, grandpa, grandma); + DeepPerson mother = new DeepPerson("Mother", "Jones", 165, false); + + // Children + DeepPerson child1 = new DeepPerson("Child1", "Smith", 140, true, father, mother); + DeepPerson child2 = new DeepPerson("Child2", "Smith", 130, false, father, mother); + + family.add(child1); + family.add(child2); + + storageManager = EmbeddedStorage.start(family, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson child1Copy = objectCopier.copy(child1); + + assertNotSame(child1, child1Copy); + assertNotSame(child1.getFather(), child1Copy.getFather()); + assertNotSame(child1.getMather(), child1Copy.getMather()); + assertNotSame(child1.getFather().getFather(), child1Copy.getFather().getFather()); + + // Verify structure is preserved + assertEquals(child1.getFather().getFirstName(), child1Copy.getFather().getFirstName()); + assertEquals(child1.getFather().getFather().getFirstName(), + child1Copy.getFather().getFather().getFirstName()); + } + + @Test + public void deepCopyModifyMultipleLevelsTest() + { + List people = new ArrayList<>(); + + DeepPerson grandpa = new DeepPerson("OldName", "Grandfather", 170, true); + DeepPerson father = new DeepPerson("Dad", "Father", 180, true, grandpa, null); + DeepPerson son = new DeepPerson("Son", "Junior", 160, true, father, null); + + people.add(son); + + storageManager = EmbeddedStorage.start(people, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson sonCopy = objectCopier.copy(son); + + // Modify at multiple levels + sonCopy.setFirstName("ModifiedSon"); + sonCopy.getFather().setFirstName("ModifiedDad"); + sonCopy.getFather().getFather().setFirstName("ModifiedGrandpa"); + + // Original should be unchanged + assertEquals("Junior", son.getFirstName()); + assertEquals("Father", son.getFather().getFirstName()); + assertEquals("Grandfather", son.getFather().getFather().getFirstName()); + + // Copy should be modified + assertEquals("ModifiedSon", sonCopy.getFirstName()); + assertEquals("ModifiedDad", sonCopy.getFather().getFirstName()); + assertEquals("ModifiedGrandpa", sonCopy.getFather().getFather().getFirstName()); + } + + @Test + public void deepCopyListOfPeopleTest() + { + List originalList = new ArrayList<>(); + + originalList.add(new DeepPerson("Person1", "First", 180, true)); + originalList.add(new DeepPerson("Person2", "Second", 170, false)); + originalList.add(new DeepPerson("Person3", "Third", 165, true)); + + storageManager = EmbeddedStorage.start(originalList, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + List copiedList = objectCopier.copy(originalList); + + assertNotSame(originalList, copiedList); + assertEquals(originalList.size(), copiedList.size()); + + for (int i = 0; i < originalList.size(); i++) { + assertNotSame(originalList.get(i), copiedList.get(i)); + assertEquals(originalList.get(i).getFirstName(), copiedList.get(i).getFirstName()); + } + + // Modify copy + copiedList.get(0).setFirstName("Modified"); + copiedList.add(new DeepPerson("New", "Person", 175, true)); + + assertEquals("First", originalList.get(0).getFirstName()); + assertEquals(3, originalList.size()); + } + + @Test + public void deepCopyWithAllPrimitiveTypesTest() + { + List dataList = new ArrayList<>(); + + ComplexData data = new ComplexData(); + data.byteValue = (byte) 127; + data.shortValue = (short) 32000; + data.intValue = 1000000; + data.longValue = 9999999999L; + data.floatValue = 3.14f; + data.doubleValue = 2.718281828; + data.charValue = 'Z'; + data.booleanValue = true; + data.stringValue = "TestString"; + + dataList.add(data); + + storageManager = EmbeddedStorage.start(dataList, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + ComplexData dataCopy = objectCopier.copy(data); + + assertNotSame(data, dataCopy); + assertEquals(data.byteValue, dataCopy.byteValue); + assertEquals(data.shortValue, dataCopy.shortValue); + assertEquals(data.intValue, dataCopy.intValue); + assertEquals(data.longValue, dataCopy.longValue); + assertEquals(data.floatValue, dataCopy.floatValue); + assertEquals(data.doubleValue, dataCopy.doubleValue); + assertEquals(data.charValue, dataCopy.charValue); + assertEquals(data.booleanValue, dataCopy.booleanValue); + assertEquals(data.stringValue, dataCopy.stringValue); + + // Modify copy + dataCopy.intValue = 999; + dataCopy.stringValue = "Modified"; + + assertEquals(1000000, data.intValue); + assertEquals("TestString", data.stringValue); + } + + @Test + public void deepCopyWithCollectionsTest() + { + List people = new ArrayList<>(); + + PersonWithCollections person = new PersonWithCollections("John", "Doe"); + person.addHobby("Reading"); + person.addHobby("Swimming"); + person.addAttribute("height", "180cm"); + person.addAttribute("weight", "75kg"); + person.addFriend(new DeepPerson("Friend1", "One", 175, true)); + person.addFriend(new DeepPerson("Friend2", "Two", 170, false)); + + people.add(person); + + storageManager = EmbeddedStorage.start(people, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + PersonWithCollections personCopy = objectCopier.copy(person); + + assertNotSame(person, personCopy); + assertNotSame(person.hobbies, personCopy.hobbies); + assertNotSame(person.attributes, personCopy.attributes); + assertNotSame(person.friends, personCopy.friends); + + assertEquals(person.hobbies.size(), personCopy.hobbies.size()); + assertTrue(personCopy.hobbies.contains("Reading")); + + // Modify copy's collections + personCopy.addHobby("Coding"); + personCopy.attributes.put("height", "185cm"); + personCopy.friends.get(0).setFirstName("ModifiedFriend"); + + assertEquals(2, person.hobbies.size()); + assertEquals(3, personCopy.hobbies.size()); + assertEquals("180cm", person.attributes.get("height")); + assertEquals("One", person.friends.get(0).getFirstName()); + } + + @Test + public void deepCopyArrayTest() + { + List dataList = new ArrayList<>(); + + DataWithArray data = new DataWithArray(); + data.numbers = new int[]{1, 2, 3, 4, 5}; + data.people = new DeepPerson[]{ + new DeepPerson("Array1", "Person1", 180, true), + new DeepPerson("Array2", "Person2", 170, false) + }; + + dataList.add(data); + + storageManager = EmbeddedStorage.start(dataList, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DataWithArray dataCopy = objectCopier.copy(data); + + assertNotSame(data, dataCopy); + assertNotSame(data.numbers, dataCopy.numbers); + assertNotSame(data.people, dataCopy.people); + + assertEquals(data.numbers.length, dataCopy.numbers.length); + assertEquals(data.people.length, dataCopy.people.length); + + for (int i = 0; i < data.people.length; i++) { + assertNotSame(data.people[i], dataCopy.people[i]); + assertEquals(data.people[i].getFirstName(), dataCopy.people[i].getFirstName()); + } + + // Modify copy + dataCopy.numbers[0] = 999; + dataCopy.people[0].setFirstName("ModifiedArray"); + + assertEquals(1, data.numbers[0]); + assertEquals("Person1", data.people[0].getFirstName()); + } + + @Test + public void deepCopySamePersonMultipleReferencesTest() + { + List people = new ArrayList<>(); + + DeepPerson sharedParent = new DeepPerson("Shared", "Parent", 180, true); + + // Two children sharing the same parent object + DeepPerson child1 = new DeepPerson("Child1", "One", 140, true, sharedParent, null); + DeepPerson child2 = new DeepPerson("Child2", "Two", 130, false, sharedParent, null); + + people.add(child1); + people.add(child2); + + storageManager = EmbeddedStorage.start(people, tempDir); + ObjectCopier objectCopier = ObjectCopier.New(); + + DeepPerson child1Copy = objectCopier.copy(child1); + DeepPerson child2Copy = objectCopier.copy(child2); + + assertNotSame(child1, child1Copy); + assertNotSame(child2, child2Copy); + assertNotSame(child1.getFather(), child1Copy.getFather()); + assertNotSame(child2.getFather(), child2Copy.getFather()); + + // Each copy should have its own parent copy + assertNotSame(child1Copy.getFather(), child2Copy.getFather()); + } + + @AfterEach + public void afterTest() + { + if (storageManager != null) { + storageManager.shutdown(); + } + } + + + static class DeepPerson + { + private String SecondName; + private String FirstName; + private Integer high; + private Boolean man; + private DeepPerson father; + private DeepPerson mather; + + + public DeepPerson(String secondName, String firstName, Integer high, Boolean man) + { + SecondName = secondName; + FirstName = firstName; + this.high = high; + this.man = man; + } + + public DeepPerson(String secondName, String firstName, Integer high, Boolean man, DeepPerson father, DeepPerson mather) + { + SecondName = secondName; + FirstName = firstName; + this.high = high; + this.man = man; + this.father = father; + this.mather = mather; + } + + public String getSecondName() + { + return SecondName; + } + + public void setSecondName(String secondName) + { + SecondName = secondName; + } + + public String getFirstName() + { + return FirstName; + } + + public void setFirstName(String firstName) + { + FirstName = firstName; + } + + public Integer getHigh() + { + return high; + } + + public void setHigh(Integer high) + { + this.high = high; + } + + public Boolean getMan() + { + return man; + } + + public void setMan(Boolean man) + { + this.man = man; + } + + public DeepPerson getFather() + { + return father; + } + + public void setFather(DeepPerson father) + { + this.father = father; + } + + public DeepPerson getMather() + { + return mather; + } + + public void setMather(DeepPerson mather) + { + this.mather = mather; + } + + @Override + public String toString() + { + return "DeepPerson{" + + "SecondName='" + SecondName + '\'' + + ", FirstName='" + FirstName + '\'' + + ", high=" + high + + ", man=" + man + + ", father=" + father + + ", mather=" + mather + + '}'; + } + } + + static class ComplexData + { + byte byteValue; + short shortValue; + int intValue; + long longValue; + float floatValue; + double doubleValue; + char charValue; + boolean booleanValue; + String stringValue; + } + + static class PersonWithCollections + { + String firstName; + String lastName; + List hobbies; + Map attributes; + List friends; + + public PersonWithCollections(String firstName, String lastName) + { + this.firstName = firstName; + this.lastName = lastName; + this.hobbies = new ArrayList<>(); + this.attributes = new HashMap<>(); + this.friends = new ArrayList<>(); + } + + public void addHobby(String hobby) + { + hobbies.add(hobby); + } + + public void addAttribute(String key, String value) + { + attributes.put(key, value); + } + + public void addFriend(DeepPerson friend) + { + friends.add(friend); + } + } + + static class DataWithArray + { + int[] numbers; + DeepPerson[] people; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/AbstractHandlerTest.java b/integration-tests/src/test/java/test/eclipse/store/entity/AbstractHandlerTest.java new file mode 100644 index 000000000..a2381fb9c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/AbstractHandlerTest.java @@ -0,0 +1,146 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.afs.types.AFile; +import org.eclipse.serializer.collections.types.XEnum; +import org.eclipse.serializer.collections.types.XSequence; +import org.eclipse.serializer.util.X; +import org.eclipse.serializer.util.cql.CQL; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageConnection; +import org.eclipse.store.storage.types.StorageEntityTypeExportFileProvider; +import org.eclipse.store.storage.types.StorageEntityTypeExportStatistics; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.types.BinaryHandlerTestData; + +public abstract class AbstractHandlerTest +{ + + private Class aClass; + + private EmbeddedStorageManager storage; + + private Path actualDirectory; + + public AbstractHandlerTest(Class aClass) + { + this.aClass = aClass; + } + + + public abstract void proveResult(T original, T copy); + + @Test + void saveAndLoadTest(@TempDir Path tempDir) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException + { + System.out.println(tempDir); + System.out.println(this); + T original = aClass.getDeclaredConstructor().newInstance(); + original.fillSampleData(); + T copy = aClass.getDeclaredConstructor().newInstance(); + saveAndReload(original, copy, tempDir); + proveResult(original, copy); + storage.shutdown(); //must be closed here, in prove result is for example lazy test and then needs to load some data from storage. + } + + + @Test + void exportImportTest(@TempDir Path tempDir) throws IllegalAccessException, InstantiationException, InterruptedException, NoSuchMethodException, InvocationTargetException + { + //System.out.println(tempDir); + //System.out.println(this); + T original = aClass.getDeclaredConstructor().newInstance(); + original.fillSampleData(); + T copy = aClass.getDeclaredConstructor().newInstance(); + + Thread.sleep(10); + actualDirectory = tempDir.resolve(String.valueOf(System.currentTimeMillis())); + storage = startStorage(original); + StorageConnection connection = storage.createConnection(); + String fileSuffix = "bin"; + assertNotNull(tempDir); + + final NioFileSystem fs = NioFileSystem.New(); + + final ADirectory dir = fs.ensureDirectoryPath(tempDir.toFile().getAbsolutePath()); + + + StorageEntityTypeExportStatistics exportResult = connection.exportTypes( + new StorageEntityTypeExportFileProvider.Default(dir, fileSuffix), + typeHandler -> true // export all, customize if necessary + ); + XSequence exportFiles = CQL + .from(exportResult.typeStatistics().values()) + .project(s -> Paths.get(s.file().identifier())) + .execute(); + storage.shutdown(); + + storage = startStorage(copy); + StorageConnection loadConnection = storage.createConnection(); + + XEnum importFiles = X.Enum(); + + for (Path p : exportFiles) { + Path fullPath = tempDir.resolve(p); + + importFiles.add(fs.ensureFile(fullPath)); + } + + loadConnection.importFiles(importFiles); + proveResult(original, copy); + storage.shutdown(); + } + + @AfterEach + void cleanStorage() throws IOException + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + FileUtils.deleteDirectory(actualDirectory.toFile()); + } + + O saveAndReload(O original, O loaded, Path targetDirectory) + { + this.actualDirectory = targetDirectory.resolve(String.valueOf(System.currentTimeMillis())); + storage = startStorage(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorage(loaded); + return loaded; + } + + private EmbeddedStorageManager startStorage(Object root) + { + return EmbeddedStorage.start(root, actualDirectory); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/Person.java b/integration-tests/src/test/java/test/eclipse/store/entity/Person.java new file mode 100644 index 000000000..b3ae95631 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/Person.java @@ -0,0 +1,25 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import org.eclipse.serializer.entity.Entity; + +public interface Person extends Entity +{ + public String firstName(); + + public String lastName(); +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/PersonCreator.java b/integration-tests/src/test/java/test/eclipse/store/entity/PersonCreator.java new file mode 100644 index 000000000..6a3404528 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/PersonCreator.java @@ -0,0 +1,84 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import org.eclipse.serializer.entity.Entity; +import org.eclipse.serializer.entity.EntityLayerIdentity; + +public interface PersonCreator extends Entity.Creator +{ + public PersonCreator firstName(String firstName); + + public PersonCreator lastName(String lastName); + + public static PersonCreator New() + { + return new Default(); + } + + public static PersonCreator New(final Person other) + { + return new Default().copy(other); + } + + public class Default extends Entity.Creator.Abstract implements PersonCreator + { + private String firstName; + private String lastName; + + protected Default() + { + super(); + } + + @Override + public PersonCreator firstName(final String firstName) + { + this.firstName = firstName; + return this; + } + + @Override + public PersonCreator lastName(final String lastName) + { + this.lastName = lastName; + return this; + } + + @Override + protected EntityLayerIdentity createEntityInstance() + { + return new PersonEntity(); + } + + @Override + public Person createData(final Person entityInstance) + { + return new PersonData(entityInstance, + this.firstName, + this.lastName); + } + + @Override + public PersonCreator copy(final Person other) + { + final Person data = Entity.data(other); + this.firstName = data.firstName(); + this.lastName = data.lastName(); + return this; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/PersonData.java b/integration-tests/src/test/java/test/eclipse/store/entity/PersonData.java new file mode 100644 index 000000000..ec193211e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/PersonData.java @@ -0,0 +1,60 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Objects; + +import org.eclipse.serializer.entity.EntityData; + +public class PersonData extends EntityData implements Person +{ + private final String firstName; + private final String lastName; + + protected PersonData(final Person entity, final String firstName, final String lastName) + { + super(entity); + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public String firstName() + { + return this.firstName; + } + + @Override + public String lastName() + { + return this.lastName; + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PersonData that = (PersonData) o; + return Objects.equals(firstName, that.firstName) && + Objects.equals(lastName, that.lastName); + } + + @Override + public int hashCode() + { + return Objects.hash(firstName, lastName); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/PersonEntity.java b/integration-tests/src/test/java/test/eclipse/store/entity/PersonEntity.java new file mode 100644 index 000000000..d45dd5b18 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/PersonEntity.java @@ -0,0 +1,43 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.serializer.entity.EntityLayerIdentity; + +public class PersonEntity extends EntityLayerIdentity implements Person +{ + protected PersonEntity() + { + super(); + } + + @Override + protected Person entityData() + { + return (Person) super.entityData(); + } + + @Override + public final String firstName() + { + return this.entityData().firstName(); + } + + @Override + public final String lastName() + { + return this.entityData().lastName(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/PersonEntityTest.java b/integration-tests/src/test/java/test/eclipse/store/entity/PersonEntityTest.java new file mode 100644 index 000000000..e3e8af5a8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/PersonEntityTest.java @@ -0,0 +1,68 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashSet; +import java.util.Set; + +import test.eclipse.serializer.fixtures.types.BinaryHandlerTestData; + + +public class PersonEntityTest extends AbstractHandlerTest +{ + + static Person create = PersonCreator.New().firstName("John").lastName("Doe").create(); + + public PersonEntityTest() + { + super(DataRoot.class); + } + + @Override + public void proveResult(DataRoot original, DataRoot copy) + { + //System.out.println(copy); + Person copyPerson = copy.getPersons().stream().findFirst().get(); + assertEquals(create.firstName(), copyPerson.firstName()); + } + + public static class DataRoot implements BinaryHandlerTestData + { + + Set persons = new HashSet<>(); + + @Override + public BinaryHandlerTestData fillSampleData() + { + persons.add(create); + return this; + } + + @Override + public void proveResults(Object o) + { + + } + + public Set getPersons() + { + return persons; + } + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/PersonHashEqualator.java b/integration-tests/src/test/java/test/eclipse/store/entity/PersonHashEqualator.java new file mode 100644 index 000000000..44b90dd98 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/PersonHashEqualator.java @@ -0,0 +1,62 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Objects; + +import org.eclipse.serializer.hashing.HashEqualator; +import org.eclipse.serializer.typing.Stateless; + +public interface PersonHashEqualator extends HashEqualator +{ + public static PersonHashEqualator New() + { + return new Default(); + } + + public final class Default implements PersonHashEqualator, Stateless + { + public static boolean equals(final Person person1, final Person person2) + { + return Objects.equals(person1.firstName(), person2.firstName()) + && Objects.equals(person1.lastName(), person2.lastName()); + } + + public static int hashCode(final Person person) + { + return Objects.hash(person.firstName(), person.lastName()); + } + + Default() + { + super(); + } + + @Override + public boolean equal(final Person person1, final Person person2) + { + return equals(person1, person2); + } + + @Override + public int hash(final Person person) + { + return hashCode(person); + + } + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/entity/PersonUpdater.java b/integration-tests/src/test/java/test/eclipse/store/entity/PersonUpdater.java new file mode 100644 index 000000000..68236e508 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/entity/PersonUpdater.java @@ -0,0 +1,84 @@ +package test.eclipse.store.entity; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import org.eclipse.serializer.entity.Entity; + +public interface PersonUpdater extends Entity.Updater +{ + public static boolean setFirstName(final Person person, final String firstName) + { + return New(person).firstName(firstName).update(); + } + + public static boolean setLastName(final Person person, final String lastName) + { + return New(person).lastName(lastName).update(); + } + + public PersonUpdater firstName(String firstName); + + public PersonUpdater lastName(String lastName); + + public static PersonUpdater New(final Person person) + { + return new Default(person); + } + + public class Default + extends Entity.Updater.Abstract + implements PersonUpdater + { + private String firstName; + private String lastName; + + protected Default(final Person person) + { + super(person); + } + + @Override + public PersonUpdater firstName(final String firstName) + { + this.firstName = firstName; + return this; + } + + @Override + public PersonUpdater lastName(final String lastName) + { + this.lastName = lastName; + return this; + } + + @Override + public Person createData(final Person entityInstance) + { + return new PersonData(entityInstance, + this.firstName, + this.lastName); + } + + @Override + public PersonUpdater copy(final Person other) + { + final Person data = Entity.data(other); + this.firstName = data.firstName(); + this.lastName = data.lastName(); + return this; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/export/Customer.java b/integration-tests/src/test/java/test/eclipse/store/export/Customer.java new file mode 100644 index 000000000..bcf250498 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/export/Customer.java @@ -0,0 +1,71 @@ +package test.eclipse.store.export; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class Customer +{ + private String firstName; + private String secondName; + private String street; + private String city; + + public Customer(String firstName, String secondName, String street, String city) + { + this.firstName = firstName; + this.secondName = secondName; + this.street = street; + this.city = city; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } + + public String getStreet() + { + return street; + } + + public void setStreet(String street) + { + this.street = street; + } + + public String getCity() + { + return city; + } + + public void setCity(String city) + { + this.city = city; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/export/CustomerGenerator.java b/integration-tests/src/test/java/test/eclipse/store/export/CustomerGenerator.java new file mode 100644 index 000000000..6073b8a82 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/export/CustomerGenerator.java @@ -0,0 +1,33 @@ +package test.eclipse.store.export; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import net.datafaker.Faker; + +public class CustomerGenerator +{ + + private static Faker faker = new Faker(); + + private CustomerGenerator() + { + // prevent instantiate + } + + public static Customer generateNewCustomer() + { + return new Customer(faker.name().firstName(), faker.name().lastName(), faker.address().streetName(), faker.address().streetAddressNumber()); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/export/ExportTest.java b/integration-tests/src/test/java/test/eclipse/store/export/ExportTest.java new file mode 100644 index 000000000..2376c1f3d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/export/ExportTest.java @@ -0,0 +1,126 @@ +package test.eclipse.store.export; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.afs.types.AFile; +import org.eclipse.serializer.afs.types.AReadableFile; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ExportTest +{ + + @TempDir + Path location; + + @Test + void exportToCSVTest() throws IOException + { + List customers = new ArrayList<>(); + customers.add(CustomerGenerator.generateNewCustomer()); + customers.add(CustomerGenerator.generateNewCustomer()); + customers.add(CustomerGenerator.generateNewCustomer()); + customers.add(CustomerGenerator.generateNewCustomer()); + customers.add(CustomerGenerator.generateNewCustomer()); + + + Path exportPathBin = location.resolve("export"); + + EmbeddedStorageManager storage = EmbeddedStorage.start(customers, location); + + final NioFileSystem fs = NioFileSystem.New(); + + final ADirectory exportPathDir = fs.ensureDirectoryPath(exportPathBin.toFile().getAbsolutePath()); + + StorageEntityTypeExportStatistics exportResult = storage.exportTypes( + new StorageEntityTypeExportFileProvider.Default(exportPathDir, "bin")); + + //get all exported binary files + List binaryFiles = new ArrayList<>(); + exportResult.typeStatistics().values().forEach(v -> binaryFiles.add(v.file().identifier())); + + Path csvPath = location.resolve("csv"); + + final ADirectory aCsvDir = fs.ensureDirectoryPath(csvPath.toFile().getAbsolutePath()); + + //create the CSV converter + StorageDataConverterTypeBinaryToCsv converter = + new StorageDataConverterTypeBinaryToCsv.UTF8( + StorageDataConverterCsvConfiguration.defaultConfiguration(), + new StorageEntityTypeConversionFileProvider.Default(aCsvDir, "csv"), + storage.typeDictionary(), + null, // no type name mapping + 4096, // read buffer size + 4096 // write buffer size + ); + + //convert all binary files to CSV files + + for (String file : binaryFiles) { + AReadableFile dataFile = exportPathDir.ensureFile(file).useReading(); + try { + converter.convertDataFile(dataFile); + } finally { + dataFile.close(); + } + } + + List files = (List) FileUtils.listFiles(csvPath.toFile(), null, true); + assertTrue(files.size() > 10); + +// //reimport + + Path reimportStoragePath = location.resolve("reimport"); + reimportStoragePath.toFile().mkdir(); + ADirectory reimportStorage = fs.ensureDirectoryPath(reimportStoragePath.toFile().getAbsolutePath()); + + StorageDataConverterTypeCsvToBinary reimportConverter = + StorageDataConverterTypeCsvToBinary.New( + StorageDataConverterCsvConfiguration.defaultConfiguration(), + storage.typeDictionary(), + new StorageEntityTypeConversionFileProvider.Default(reimportStorage, "dat") + ); + + for (File file : files) { + AReadableFile storageFile = aCsvDir.ensureFile(file.getName()).useReading(); + try { + reimportConverter.convertCsv(storageFile); + } catch (Exception e) { + e.printStackTrace(); + } finally { + storageFile.close(); + } + } + List datFiles = (List) FileUtils.listFiles(reimportStoragePath.toFile(), null, true); + assertTrue(datFiles.size() > 10, files.toString()); + + storage.shutdown(); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/GeoTest.java b/integration-tests/src/test/java/test/eclipse/store/geo/GeoTest.java new file mode 100644 index 000000000..b414f6305 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/GeoTest.java @@ -0,0 +1,99 @@ +package test.eclipse.store.geo; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.geo.data.Country; +import test.eclipse.store.geo.data.Geo; +import test.eclipse.store.geo.data.generator.Generator; +import test.eclipse.store.geo.data.generator.GeneratorAT; + +public class GeoTest +{ + @TempDir + Path tempDir; + + //@RepeatedTest(10) + @Test + void geoDataStoreTest() + { + Geo geo = Generator.generateGeo(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(geo, tempDir)) { + } + + + Geo loadedGeo = new Geo(); + + Geo finalLoadedGeo = loadedGeo; + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + loadedGeo = (Geo) storageManager.root(); + //System.out.println(loadedGeo.toString()); + for (int i = 0; i < 100; i++) { + Country austria = new Country("Austria", GeneratorAT.generateATStates()); + loadedGeo.getCountries().add(austria); + storageManager.store(loadedGeo.getCountries()); + } + } + +// System.out.println("================= After adding Austria ================"); +// try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { +// loadedGeo = (Geo) storageManager.root(); +// int size = loadedGeo.getCountries().size(); +// System.out.println("XXX: Number of countries first store: " + size); +// //System.out.println(loadedGeo.toString()); +// } +// +// try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { +// loadedGeo = (Geo) storageManager.root(); +// List countries = loadedGeo.getCountries(); +// countries.remove(countries.size() - 1); +// countries.remove(countries.size() - 1); +// countries.remove(countries.size() - 1); +// countries.remove(countries.size() - 1); +// countries.remove(countries.size() - 1); +// countries.remove(countries.size() - 1); +// storageManager.store(loadedGeo.getCountries()); +// int size = loadedGeo.getCountries().size(); +// System.out.println("XXX: Number of countries after remove: " + size); +// +// } +// +// try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { +// loadedGeo = (Geo) storageManager.root(); +// //System.out.println(loadedGeo.toString()); +//// int size = loadedGeo.getCountries().size(); +//// System.out.println("XXX: Number of countries final load: " + size); +// +// } + + + // print all folder and files from tempdir; +// System.out.println("Files in tempDir:"); +// try { +// java.nio.file.Files.walk(tempDir) +// .forEach(path -> System.out.println(path.toString())); +// } catch (Exception e) { +// e.printStackTrace(); +// } + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/City.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/City.java new file mode 100644 index 000000000..f1febc35f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/City.java @@ -0,0 +1,36 @@ +package test.eclipse.store.geo.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class City +{ + String name; + long population; + + public City(String name, long population) + { + this.name = name; + this.population = population; + } + + @Override + public String toString() + { + return "City{" + + "name='" + name + '\'' + + ", population=" + population + + "}\n"; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/Country.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/Country.java new file mode 100644 index 000000000..703984b65 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/Country.java @@ -0,0 +1,48 @@ +package test.eclipse.store.geo.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.List; + +public class Country +{ + String name; + List states; + + public Country(String name, List states) + { + this.name = name; + this.states = states; + } + + public String getName() + { + return name; + } + + public List getStates() + { + return states; + } + + @Override + public String toString() + { + return "Country{" + + "name='" + name + '\'' + + ", states=" + states + + "}\n:"; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/Geo.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/Geo.java new file mode 100644 index 000000000..3785975c8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/Geo.java @@ -0,0 +1,56 @@ +package test.eclipse.store.geo.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.List; + +public class Geo +{ + String name; + List countries; + + public Geo(String name) + { + this.name = name; + } + + public Geo() + { + } + + public Geo(String name, List countries) + { + this.name = name; + this.countries = countries; + } + + public Geo(List countries) + { + this.countries = countries; + } + + public List getCountries() + { + return countries; + } + + @Override + public String toString() + { + return "Geo{" + + "states=" + countries + + "}\n:"; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/State.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/State.java new file mode 100644 index 000000000..22ab96954 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/State.java @@ -0,0 +1,64 @@ +package test.eclipse.store.geo.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.List; + +public class State +{ + String name; + String capital; + long population; + List cities; + + public State(String name, String capital, long population, List cities) + { + this.name = name; + this.capital = capital; + this.population = population; + this.cities = cities; + } + + public String getName() + { + return name; + } + + public String getCapital() + { + return capital; + } + + public long getPopulation() + { + return population; + } + + public List getCities() + { + return cities; + } + + @Override + public String toString() + { + return "State{" + + "name='" + name + '\'' + + ", capital='" + capital + '\'' + + ", population=" + population + + ", cities=" + cities + + "}\n:"; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/Generator.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/Generator.java new file mode 100644 index 000000000..bac848b65 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/Generator.java @@ -0,0 +1,86 @@ +package test.eclipse.store.geo.data.generator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import test.eclipse.store.geo.data.City; +import test.eclipse.store.geo.data.Country; +import test.eclipse.store.geo.data.Geo; + + +public class Generator +{ + + public static Geo generateGeo() + { + return new Geo(generateCountries()); + } + + + public static List generateCountries() + { + Country czechia = new Country("Czechia", GeneratorCZ.generateCZStates()); + Country germany = new Country("Germany", GeneratorDE.generateDEStates()); + ArrayList countries = new ArrayList(); + countries.add(czechia); + countries.add(germany); + return countries; + } + + + public static List generateBavariaCities() + { + List bavariaCities = Arrays.asList( + new City("Munich", 1500000), + new City("Nuremberg", 520000), + new City("Augsburg", 300000), + new City("Regensburg", 155000), + new City("Ingolstadt", 140000), + new City("Würzburg", 130000), + new City("Fürth", 130000), + new City("Erlangen", 115000), + new City("Bayreuth", 75000), + new City("Bamberg", 80000), + new City("Aschaffenburg", 71000), + new City("Rosenheim", 65000), + new City("Landshut", 75000), + new City("Passau", 55000), + new City("Kempten (Allgäu)", 70000), + new City("Hof", 46000), + new City("Schweinfurt", 53000), + new City("Straubing", 48000), + new City("Neu-Ulm", 60000), + new City("Memmingen", 46000), + new City("Dachau", 48000), + new City("Freising", 50000), + new City("Kaufbeuren", 45000), + new City("Ansbach", 42000), + new City("Weiden in der Oberpfalz", 43000), + new City("Lindau (Bodensee)", 26000), + new City("Amberg", 42000), + new City("Deggendorf", 38000), + new City("Neuburg an der Donau", 30000), + new City("Schwabach", 41000) + ); + + return bavariaCities; + } +} + + + diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorAT.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorAT.java new file mode 100644 index 000000000..4266e5ee4 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorAT.java @@ -0,0 +1,175 @@ +package test.eclipse.store.geo.data.generator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Arrays; +import java.util.List; + +import test.eclipse.store.geo.data.City; +import test.eclipse.store.geo.data.State; + + +public class GeneratorAT +{ + public static List generateATStates() + { + State vienna = new State("Wien", "Wien", 1970000, generateViennaCities()); + State lowerAustria = new State("Niederösterreich", "St. Pölten", 1700000, generateLowerAustriaCities()); + State upperAustria = new State("Oberösterreich", "Linz", 1500000, generateUpperAustriaCities()); + State styria = new State("Steiermark", "Graz", 1250000, generateStyriaCities()); + State tyrol = new State("Tirol", "Innsbruck", 760000, generateTyrolCities()); + State carinthia = new State("Kärnten", "Klagenfurt am Wörthersee", 560000, generateCarinthiaCities()); + State salzburg = new State("Salzburg", "Salzburg", 560000, generateSalzburgCities()); + State vorarlberg = new State("Vorarlberg", "Bregenz", 410000, generateVorarlbergCities()); + State burgenland = new State("Burgenland", "Eisenstadt", 300000, generateBurgenlandCities()); + + return Arrays.asList(vienna, lowerAustria, upperAustria, styria, tyrol, carinthia, salzburg, vorarlberg, burgenland); + } + + public static List generateViennaCities() + { + return Arrays.asList( + new City("Wien", 1970000) + ); + } + + public static List generateLowerAustriaCities() + { + return Arrays.asList( + new City("St. Pölten", 55000), + new City("Wiener Neustadt", 47000), + new City("Baden", 26000), + new City("Krems an der Donau", 25000), + new City("Amstetten", 24000), + new City("Mödling", 20000), + new City("Klosterneuburg", 27000), + new City("Stockerau", 17000), + new City("Tulln an der Donau", 17000), + new City("Schwechat", 18000) + ); + } + + public static List generateUpperAustriaCities() + { + return Arrays.asList( + new City("Linz", 205000), + new City("Wels", 62000), + new City("Steyr", 38000), + new City("Leonding", 28000), + new City("Traun", 25000), + new City("Ansfelden", 17000), + new City("Marchtrenk", 14000), + new City("Bad Ischl", 14000), + new City("Vöcklabruck", 12500), + new City("Gmunden", 13000) + ); + } + + public static List generateStyriaCities() + { + return Arrays.asList( + new City("Graz", 300000), + new City("Leoben", 25000), + new City("Kapfenberg", 22000), + new City("Bruck an der Mur", 16000), + new City("Feldbach", 13000), + new City("Voitsberg", 9500), + new City("Knittelfeld", 12500), + new City("Hartberg", 6500), + new City("Weiz", 11000), + new City("Deutschlandsberg", 12000) + ); + } + + public static List generateTyrolCities() + { + return Arrays.asList( + new City("Innsbruck", 130000), + new City("Kufstein", 20000), + new City("Schwaz", 13000), + new City("Hall in Tirol", 14000), + new City("Telfs", 16000), + new City("Lienz", 12000), + new City("Wörgl", 14000), + new City("Imst", 11000), + new City("Reutte", 6600), + new City("Kitzbühel", 8300) + ); + } + + public static List generateCarinthiaCities() + { + return Arrays.asList( + new City("Klagenfurt am Wörthersee", 103000), + new City("Villach", 65000), + new City("Wolfsberg", 25000), + new City("Spittal an der Drau", 16000), + new City("St. Veit an der Glan", 12000), + new City("Feldkirchen in Kärnten", 14000), + new City("Hermagor", 7000), + new City("Völkermarkt", 11000), + new City("Bleiburg", 4000), + new City("Radenthein", 6000) + ); + } + + public static List generateSalzburgCities() + { + return Arrays.asList( + new City("Salzburg", 155000), + new City("Hallein", 21000), + new City("Bischofshofen", 11000), + new City("St. Johann im Pongau", 11000), + new City("Saalfelden am Steinernen Meer", 17000), + new City("Zell am See", 10000), + new City("Seekirchen am Wallersee", 11000), + new City("Mittersill", 5600), + new City("Oberndorf bei Salzburg", 6000), + new City("Neumarkt am Wallersee", 6000) + ); + } + + public static List generateVorarlbergCities() + { + return Arrays.asList( + new City("Bregenz", 30000), + new City("Dornbirn", 50000), + new City("Feldkirch", 35000), + new City("Bludenz", 14000), + new City("Hohenems", 17000), + new City("Lustenau", 23000), + new City("Rankweil", 12000), + new City("Götzis", 11000), + new City("Hard", 13000), + new City("Altach", 7000) + ); + } + + public static List generateBurgenlandCities() + { + return Arrays.asList( + new City("Eisenstadt", 15000), + new City("Oberwart", 7000), + new City("Mattersburg", 7200), + new City("Neusiedl am See", 8000), + new City("Jennersdorf", 4200), + new City("Pinkafeld", 5500), + new City("Rust", 2000), + new City("Güssing", 3700), + new City("Frauenkirchen", 3000), + new City("Parndorf", 5100) + ); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorCZ.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorCZ.java new file mode 100644 index 000000000..cd45cc3ab --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorCZ.java @@ -0,0 +1,271 @@ +package test.eclipse.store.geo.data.generator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Arrays; +import java.util.List; + +import test.eclipse.store.geo.data.City; +import test.eclipse.store.geo.data.State; + + +public class GeneratorCZ +{ + public static List generateCZStates() + { + State midBohemia = new State("Středočeský kraj", "Prague", 1300000, generateMidBohemiaCities()); + State plzenRegion = new State("Plzeňský kraj", "Plzeň", 580000, generatePlzenRegionCities()); + State southBohemia = new State("Jihočeský kraj", "České Budějovice", 640000, generateSouthBohemiaCities()); + State karlovyVaryRegion = new State("Karlovarský kraj", "Karlovy Vary", 300000, generateKarlovyVaryCities()); + State ustiRegion = new State("Ústecký kraj", "Ústí nad Labem", 820000, generateUstiCities()); + State liberecRegion = new State("Liberecký kraj", "Liberec", 450000, generateLiberecCities()); + State hradecRegion = new State("Královéhradecký kraj", "Hradec Králové", 550000, generateHradecCities()); + State pardubiceRegion = new State("Pardubický kraj", "Pardubice", 520000, generatePardubiceCities()); + State vysocinaRegion = new State("Kraj Vysočina", "Jihlava", 500000, generateVysocinaCities()); + State southMoravia = new State("Jihomoravský kraj", "Brno", 1150000, generateSouthMoraviaCities()); + State olomoucRegion = new State("Olomoucký kraj", "Olomouc", 630000, generateOlomoucCities()); + State moravianSilesian = new State("Moravskoslezský kraj", "Ostrava", 1200000, generateMoravianSilesianCities()); + State zlinRegion = new State("Zlínský kraj", "Zlín", 580000, generateZlinCities()); + return Arrays.asList(midBohemia, plzenRegion, southBohemia, karlovyVaryRegion, ustiRegion, liberecRegion, hradecRegion, + pardubiceRegion, vysocinaRegion, southMoravia, olomoucRegion, moravianSilesian, zlinRegion); + } + + + public static List generateZlinCities() + { + return Arrays.asList( + new City("Zlín", 74000), + new City("Uherské Hradiště", 25000), + new City("Vsetín", 25000), + new City("Kroměříž", 28000), + new City("Otrokovice", 18000), + new City("Rožnov pod Radhoštěm", 16000), + new City("Holešov", 12000), + new City("Valašské Meziříčí", 22000), + new City("Napajedla", 7200), + new City("Bystřice pod Hostýnem", 8500) + ); + } + + public static List generateMoravianSilesianCities() + { + return Arrays.asList( + new City("Ostrava", 285000), + new City("Opava", 56000), + new City("Frýdek-Místek", 55000), + new City("Karviná", 50000), + new City("Havířov", 70000), + new City("Třinec", 35000), + new City("Nový Jičín", 23000), + new City("Kopřivnice", 22000), + new City("Bohumín", 20000), + new City("Orlová", 28000) + ); + } + + public static List generateOlomoucCities() + { + return Arrays.asList( + new City("Olomouc", 100000), + new City("Přerov", 42000), + new City("Prostějov", 44000), + new City("Šumperk", 25000), + new City("Hranice", 18000), + new City("Jeseník", 11000), + new City("Zábřeh", 14000), + new City("Mohelnice", 9500), + new City("Litovel", 10000), + new City("Uničov", 11000) + ); + } + + public static List generateSouthMoraviaCities() + { + return Arrays.asList( + new City("Brno", 380000), + new City("Znojmo", 34000), + new City("Břeclav", 25000), + new City("Hodonín", 24000), + new City("Vyškov", 21000), + new City("Blansko", 20000), + new City("Kuřim", 11000), + new City("Boskovice", 11000), + new City("Mikulov", 7500), + new City("Kyjov", 11000) + ); + } + + public static List generateVysocinaCities() + { + return Arrays.asList( + new City("Jihlava", 50000), + new City("Třebíč", 35000), + new City("Havlíčkův Brod", 23000), + new City("Žďár nad Sázavou", 21000), + new City("Nové Město na Moravě", 10000), + new City("Pelhřimov", 16000), + new City("Chotěboř", 10000), + new City("Telč", 5300), + new City("Humpolec", 11000), + new City("Světlá nad Sázavou", 6500) + ); + } + + public static List generatePardubiceCities() + { + return Arrays.asList( + new City("Pardubice", 90000), + new City("Chrudim", 23000), + new City("Svitavy", 17000), + new City("Ústí nad Orlicí", 15000), + new City("Litomyšl", 10000), + new City("Moravská Třebová", 10000), + new City("Vysoké Mýto", 12000), + new City("Lanškroun", 10000), + new City("Polička", 9000), + new City("Žamberk", 6000) + ); + } + + public static List generateHradecCities() + { + return Arrays.asList( + new City("Hradec Králové", 93000), + new City("Trutnov", 31000), + new City("Náchod", 20000), + new City("Jičín", 17000), + new City("Dvůr Králové nad Labem", 16000), + new City("Nový Bydžov", 7200), + new City("Jaroměř", 12000), + new City("Hořice", 9000), + new City("Rychnov nad Kněžnou", 12000), + new City("Vrchlabí", 13000) + ); + } + + public static List generateLiberecCities() + { + return Arrays.asList( + new City("Liberec", 100000), + new City("Jablonec nad Nisou", 45000), + new City("Česká Lípa", 37000), + new City("Turnov", 14000), + new City("Semily", 8700), + new City("Nový Bor", 12000), + new City("Frýdlant", 7600), + new City("Tanvald", 6600), + new City("Železný Brod", 6200), + new City("Hrádek nad Nisou", 7500) + ); + } + + public static List generateUstiCities() + { + return Arrays.asList( + new City("Ústí nad Labem", 93000), + new City("Most", 67000), + new City("Teplice", 50000), + new City("Děčín", 48000), + new City("Chomutov", 48000), + new City("Litvínov", 24000), + new City("Litoměřice", 24000), + new City("Žatec", 19000), + new City("Roudnice nad Labem", 13000), + new City("Bílina", 15000) + ); + } + + + public static List generateKarlovyVaryCities() + { + return Arrays.asList( + new City("Karlovy Vary", 50000), + new City("Cheb", 31000), + new City("Sokolov", 24000), + new City("Ostrov", 17000), + new City("Chodov", 14000), + new City("Aš", 12000), + new City("Mariánské Lázně", 13000), + new City("Františkovy Lázně", 6000), + new City("Nejdek", 8000), + new City("Kraslice", 6500) + ); + } + + + public static List generateSouthBohemiaCities() + { + List southBohemia = Arrays.asList( + new City("České Budějovice", 94000), + new City("Tábor", 34000), + new City("Písek", 30000), + new City("Jindřichův Hradec", 21000), + new City("Strakonice", 23000), + new City("Třeboň", 9000), + new City("Prachatice", 11000), + new City("Vimperk", 7500), + new City("Soběslav", 8000), + new City("Sezimovo Ústí", 7000) + ); + return southBohemia; + } + + + public static List generatePlzenRegionCities() + { + List plzenRegion = Arrays.asList( + new City("Plzeň", 175000), + new City("Klatovy", 23000), + new City("Domažlice", 11000), + new City("Rokycany", 14000), + new City("Tachov", 12500), + new City("Sušice", 11000), + new City("Stod", 5000), + new City("Nýřany", 7000), + new City("Blovice", 3800), + new City("Horšovský Týn", 5000) + ); + return plzenRegion; + } + + + public static List generateMidBohemiaCities() + { + List midBohemia = Arrays.asList( + new City("Kladno", 68600), + new City("Mladá Boleslav", 45500), + new City("Příbram", 34800), + new City("Kolín", 30000), + new City("Kralupy nad Vltavou", 18000), + new City("Neratovice", 17000), + new City("Beroun", 18000), + new City("Brandýs nad Labem-Stará Boleslav", 17000), + new City("Český Brod", 10200), + new City("Poděbrady", 15000), + new City("Říčany", 16000), + new City("Kutná Hora", 21000), + new City("Benešov", 16000), + new City("Rakovník", 16000), + new City("Slaný", 17000), + new City("Řevnice", 4300), + new City("Jílové u Prahy", 3800), + new City("Mnichovo Hradiště", 8700), + new City("Dobříš", 11700), + new City("Řepy", 4200) + ); + return midBohemia; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorDE.java b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorDE.java new file mode 100644 index 000000000..05056c8b9 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/geo/data/generator/GeneratorDE.java @@ -0,0 +1,324 @@ +package test.eclipse.store.geo.data.generator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Arrays; +import java.util.List; + +import test.eclipse.store.geo.data.City; +import test.eclipse.store.geo.data.State; + + +public class GeneratorDE +{ + + + public static List generateDEStates() + { + State bavaria = new State("Bavaria", "Munich", 13000000, generateBavariaCities()); + State badenWuerttemberg = new State("Baden-Württemberg", "Stuttgart", 11200000, generateBadenWuerttembergCities()); + State berlin = new State("Berlin", "Berlin", 3700000, generateBerlinCities()); + State saarland = new State("Saarland", "Saarbrücken", 990000, generateSaarlandCities()); + State brandenburg = new State("Brandenburg", "Potsdam", 2500000, generateBrandenburgCities()); + State bremen = new State("Bremen", "Bremen", 680000, generateBremenCities()); + State hamburg = new State("Hamburg", "Hamburg", 1900000, generateHamburgCities()); + State hessen = new State("Hessen", "Wiesbaden", 6300000, generateHessenCities()); + State mecklenburg = new State("Mecklenburg-Vorpommern", "Schwerin", 1600000, generateMecklenburgCities()); + State nrw = new State("Nordrhein-Westfalen", "Düsseldorf", 17900000, generateNRWCities()); + State saxony = new State("Sachsen", "Dresden", 4050000, generateSaxonyCities()); + State rhinelandPalatinate = new State("Rheinland-Pfalz", "Mainz", 4100000, generateRhinelandPalatinateCities()); + State saxonyAnhalt = new State("Sachsen-Anhalt", "Magdeburg", 2200000, generateSaxonyAnhaltCities()); + State schleswigHolstein = new State("Schleswig-Holstein", "Kiel", 2900000, generateSchleswigHolsteinCities()); + State thuringia = new State("Thüringen", "Erfurt", 2100000, generateThuringiaCities()); + State lowerSaxony = new State("Niedersachsen", "Hannover", 8000000, generateLowerSaxonyCities()); + + + return Arrays.asList(bavaria, badenWuerttemberg, berlin, brandenburg, bremen, hamburg, hessen, mecklenburg, + nrw, saxony, rhinelandPalatinate, saxonyAnhalt, schleswigHolstein, thuringia, lowerSaxony); + } + + // Baden-Württemberg + + public static List generateBadenWuerttembergCities() + { + return Arrays.asList( + new City("Stuttgart", 630000), + new City("Mannheim", 310000), + new City("Karlsruhe", 310000), + new City("Freiburg im Breisgau", 230000), + new City("Heidelberg", 160000), + new City("Heilbronn", 130000), + new City("Ulm", 125000), + new City("Pforzheim", 125000), + new City("Reutlingen", 115000), + new City("Tübingen", 90000) + ); + } + + // Bayern + + public static List generateBavariaCities() + { + return Arrays.asList( + new City("München", 1500000), + new City("Nürnberg", 510000), + new City("Augsburg", 300000), + new City("Regensburg", 150000), + new City("Ingolstadt", 140000), + new City("Fürth", 130000), + new City("Würzburg", 130000), + new City("Erlangen", 110000), + new City("Bayreuth", 75000), + new City("Bamberg", 78000) + ); + } + + // Berlin + + public static List generateBerlinCities() + { + return Arrays.asList( + new City("Berlin", 3700000) + ); + } + + // Brandenburg + + + public static List generateBrandenburgCities() + { + return Arrays.asList( + new City("Potsdam", 185000), + new City("Cottbus", 100000), + new City("Brandenburg an der Havel", 72000), + new City("Frankfurt (Oder)", 58000), + new City("Oranienburg", 45000), + new City("Eberswalde", 42000), + new City("Bernau bei Berlin", 42000), + new City("Fürstenwalde", 33000), + new City("Neuruppin", 32000), + new City("Schwedt", 30000) + ); + } + + // Bremen + + + public static List generateBremenCities() + { + return Arrays.asList( + new City("Bremen", 560000), + new City("Bremerhaven", 120000) + ); + } + + // Hamburg + + + public static List generateHamburgCities() + { + return Arrays.asList( + new City("Hamburg", 1900000) + ); + } + + // Hessen + + + public static List generateHessenCities() + { + return Arrays.asList( + new City("Frankfurt am Main", 770000), + new City("Wiesbaden", 280000), + new City("Kassel", 200000), + new City("Darmstadt", 160000), + new City("Offenbach am Main", 140000), + new City("Hanau", 100000), + new City("Gießen", 90000), + new City("Marburg", 77000), + new City("Fulda", 70000), + new City("Rüsselsheim", 65000) + ); + } + + // Mecklenburg-Vorpommern + + + public static List generateMecklenburgCities() + { + return Arrays.asList( + new City("Schwerin", 95000), + new City("Rostock", 210000), + new City("Neubrandenburg", 65000), + new City("Greifswald", 60000), + new City("Stralsund", 59000), + new City("Wismar", 42000), + new City("Güstrow", 30000), + new City("Waren (Müritz)", 21000), + new City("Parchim", 17000), + new City("Ribnitz-Damgarten", 15000) + ); + } + + // Niedersachsen + + public static List generateLowerSaxonyCities() + { + return Arrays.asList( + new City("Hannover", 540000), + new City("Braunschweig", 250000), + new City("Oldenburg", 170000), + new City("Osnabrück", 165000), + new City("Wolfsburg", 125000), + new City("Göttingen", 120000), + new City("Salzgitter", 100000), + new City("Hildesheim", 100000), + new City("Lüneburg", 75000), + new City("Emden", 50000) + ); + } + + // Nordrhein-Westfalen + + + public static List generateNRWCities() + { + return Arrays.asList( + new City("Köln", 1100000), + new City("Düsseldorf", 620000), + new City("Dortmund", 600000), + new City("Essen", 580000), + new City("Duisburg", 500000), + new City("Bochum", 365000), + new City("Wuppertal", 355000), + new City("Bielefeld", 340000), + new City("Bonn", 330000), + new City("Münster", 320000) + ); + } + + // Rheinland-Pfalz + + + public static List generateRhinelandPalatinateCities() + { + return Arrays.asList( + new City("Mainz", 220000), + new City("Ludwigshafen am Rhein", 170000), + new City("Koblenz", 115000), + new City("Trier", 110000), + new City("Kaiserslautern", 100000), + new City("Worms", 85000), + new City("Neuwied", 65000), + new City("Speyer", 50000), + new City("Landau in der Pfalz", 47000), + new City("Frankenthal", 48000) + ); + } + + // Saarland + public static List generateSaarlandCities() + { + return Arrays.asList( + new City("Saarbrücken", 180000), + new City("Neunkirchen", 47000), + new City("Homburg", 42000), + new City("Völklingen", 39000), + new City("St. Ingbert", 37000), + new City("Saarlouis", 35000), + new City("Merzig", 30000), + new City("Blieskastel", 22000), + new City("Sankt Wendel", 26000), + new City("Dillingen/Saar", 20000) + ); + } + + // Sachsen + + + public static List generateSaxonyCities() + { + return Arrays.asList( + new City("Dresden", 560000), + new City("Leipzig", 620000), + new City("Chemnitz", 245000), + new City("Zwickau", 90000), + new City("Plauen", 65000), + new City("Görlitz", 56000), + new City("Freiberg", 42000), + new City("Pirna", 38000), + new City("Bautzen", 39000), + new City("Hoyerswerda", 32000) + ); + } + + // Sachsen-Anhalt + + + public static List generateSaxonyAnhaltCities() + { + return Arrays.asList( + new City("Magdeburg", 240000), + new City("Halle (Saale)", 240000), + new City("Dessau-Roßlau", 80000), + new City("Wittenberg", 47000), + new City("Bernburg", 34000), + new City("Halberstadt", 40000), + new City("Stendal", 40000), + new City("Merseburg", 34000), + new City("Naumburg (Saale)", 33000), + new City("Bitterfeld-Wolfen", 37000) + ); + } + + // Schleswig-Holstein + + + public static List generateSchleswigHolsteinCities() + { + return Arrays.asList( + new City("Kiel", 250000), + new City("Lübeck", 220000), + new City("Flensburg", 90000), + new City("Neumünster", 80000), + new City("Norderstedt", 80000), + new City("Elmshorn", 50000), + new City("Pinneberg", 50000), + new City("Itzehoe", 32000), + new City("Wedel", 33000), + new City("Ahrensburg", 32000) + ); + } + + // Thüringen + + + public static List generateThuringiaCities() + { + return Arrays.asList( + new City("Erfurt", 215000), + new City("Jena", 110000), + new City("Gera", 95000), + new City("Weimar", 65000), + new City("Gotha", 45000), + new City("Eisenach", 42000), + new City("Suhl", 35000), + new City("Nordhausen", 42000), + new City("Ilmenau", 38000), + new City("Meiningen", 21000) + ); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/AbstractHandlerTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/AbstractHandlerTest.java new file mode 100644 index 000000000..49f584979 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/AbstractHandlerTest.java @@ -0,0 +1,143 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.commons.io.FileUtils; +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.afs.types.AFile; +import org.eclipse.serializer.collections.types.XEnum; +import org.eclipse.serializer.collections.types.XSequence; +import org.eclipse.serializer.util.X; +import org.eclipse.serializer.util.cql.CQL; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageConnection; +import org.eclipse.store.storage.types.StorageEntityTypeExportFileProvider; +import org.eclipse.store.storage.types.StorageEntityTypeExportStatistics; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public abstract class AbstractHandlerTest +{ + + private Class aClass; + + private EmbeddedStorageManager storage; + + private Path actualDirectory; + + public AbstractHandlerTest(Class aClass) + { + this.aClass = aClass; + } + + + public abstract void proveResult(T original, T copy); + + @Test + void saveAndLoadTest(@TempDir Path tempDir) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException + { + System.out.println(tempDir); + System.out.println(this); + T original = aClass.getDeclaredConstructor().newInstance(); + original.fillSampleData(); + T copy = aClass.getDeclaredConstructor().newInstance(); + saveAndReload(original, copy, tempDir); + proveResult(original, copy); + storage.shutdown(); //must be closed here, in prove result is for example lazy test and then needs to load some data from storage. + } + + + @Test + void exportImportTest(@TempDir Path tempDir) throws IllegalAccessException, InstantiationException, InterruptedException, NoSuchMethodException, InvocationTargetException + { + //System.out.println(tempDir); + //System.out.println(this); + T original = aClass.getDeclaredConstructor().newInstance(); + original.fillSampleData(); + T copy = aClass.getDeclaredConstructor().newInstance(); + + Thread.sleep(10); + actualDirectory = tempDir.resolve(String.valueOf(System.currentTimeMillis())); + storage = startStorage(original); + StorageConnection connection = storage.createConnection(); + String fileSuffix = "bin"; + assertNotNull(tempDir); + + final NioFileSystem fs = NioFileSystem.New(); + + final ADirectory dir = fs.ensureDirectoryPath(tempDir.toFile().getAbsolutePath()); + + + StorageEntityTypeExportStatistics exportResult = connection.exportTypes( + new StorageEntityTypeExportFileProvider.Default(dir, fileSuffix), + typeHandler -> true // export all, customize if necessary + ); + XSequence exportFiles = CQL + .from(exportResult.typeStatistics().values()) + .project(s -> Paths.get(s.file().identifier())) + .execute(); + storage.shutdown(); + + storage = startStorage(copy); + StorageConnection loadConnection = storage.createConnection(); + + XEnum importFiles = X.Enum(); + + for (Path p : exportFiles) { + Path fullPath = tempDir.resolve(p); + + importFiles.add(fs.ensureFile(fullPath)); + } + + loadConnection.importFiles(importFiles); + proveResult(original, copy); + storage.shutdown(); + } + + @AfterEach + void cleanStorage() throws IOException + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + FileUtils.deleteDirectory(actualDirectory.toFile()); + } + + O saveAndReload(O original, O loaded, Path targetDirectory) + { + this.actualDirectory = targetDirectory.resolve(String.valueOf(System.currentTimeMillis())); + storage = startStorage(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorage(loaded); + return loaded; + } + + private EmbeddedStorageManager startStorage(Object root) + { + return EmbeddedStorage.start(root, actualDirectory); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerEnumMapTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerEnumMapTest.java new file mode 100644 index 000000000..ef95e433d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerEnumMapTest.java @@ -0,0 +1,68 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.EnumMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; + +@Disabled +class BinaryHandlerEnumMapTest extends AbstractHandlerTest +{ + + BinaryHandlerEnumMapTest() + { + super(EnumMapData.class); + } + + @Override + public void proveResult(EnumMapData original, EnumMapData copy) + { + Assertions.assertEquals(original.getOriginal(), copy.getOriginal()); + } + + + static enum Tasks + { + WORK(10), HOBBY(20); + + private int value; + + Tasks(int value) + { + this.value = value; + } + } + + static class EnumMapData implements BinaryHandlerTestData + { + Map original = new EnumMap<>(Tasks.class); + + @Override + public void fillSampleData() + { + original.put(Tasks.WORK, "work"); + original.put(Tasks.HOBBY, "hobby"); + } + + public Map getOriginal() + { + return original; + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerEnumSetTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerEnumSetTest.java new file mode 100644 index 000000000..151de14f2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerEnumSetTest.java @@ -0,0 +1,67 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.EnumSet; +import java.util.Set; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; + +@Disabled +class BinaryHandlerEnumSetTest extends AbstractHandlerTest +{ + + BinaryHandlerEnumSetTest() + { + super(EnumSetData.class); + } + + @Override + public void proveResult(EnumSetData original, EnumSetData copy) + { + Assertions.assertEquals(original.getOriginal(), copy.getOriginal()); + } + + + static enum Tasks + { + WORK(10), HOBBY(20); + + private int value; + + Tasks(int value) + { + this.value = value; + } + } + + static class EnumSetData implements BinaryHandlerTestData + { + Set original; + + @Override + public void fillSampleData() + { + original = EnumSet.of(Tasks.WORK, Tasks.HOBBY); + } + + public Set getOriginal() + { + return original; + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerFile.java b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerFile.java new file mode 100644 index 000000000..96dd9ae04 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerFile.java @@ -0,0 +1,57 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.io.File; +import java.io.IOException; +import java.util.Date; + +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Assertions; + +class BinaryHandlerFile extends AbstractHandlerTest +{ + + BinaryHandlerFile() + { + super(FileData.class); + } + + @Override + public void proveResult(FileData original, FileData copy) + { + Assertions.assertEquals(original.getValue().getName(), copy.getValue().getName()); + } + + static class FileData implements BinaryHandlerTestData + { + File value; + + public void fillSampleData() + { + try { + String fileName = String.valueOf(new Date().getTime()); + value = File.createTempFile(fileName, ".txt", FileUtils.getTempDirectory()); + } catch (IOException e) { + throw new RuntimeException("Test Failed + e", e); + } + } + + File getValue() + { + return value; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerPriorityQueueTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerPriorityQueueTest.java new file mode 100644 index 000000000..c8f4f88f9 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerPriorityQueueTest.java @@ -0,0 +1,49 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.PriorityQueue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerPriorityQueueTest +{ + + @TempDir + Path storagePath; + + /** + * test for issue: https://github.com/microstream-one/microstream-private/issues/580 + * Load empty PriorityQueue + */ + @Test + public void saveAndLoadTest() + { + + PriorityQueue queue = new PriorityQueue<>(); + PriorityQueue copy = new PriorityQueue<>(); + + EmbeddedStorageManager storageManager = EmbeddedStorage.start(queue, storagePath); + storageManager.shutdown(); + + storageManager = EmbeddedStorage.start(copy, storagePath); + + storageManager.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerTest.java new file mode 100644 index 000000000..f4f1b79a2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerTest.java @@ -0,0 +1,67 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import test.eclipse.serializer.fixtures.TypeEnum; + +public class BinaryHandlerTest +{ + + @TempDir + Path storagePath; + + @ParameterizedTest + @EnumSource(TypeEnum.class) + public void saveAndLoadTest(TypeEnum type) + { + + EmbeddedStorageManager storageManager = EmbeddedStorage.start(type.getOriginal(), storagePath); + storageManager.shutdown(); + + storageManager = EmbeddedStorage.start(storagePath); + Object o = storageManager.root(); + + type.getOriginal().proveResults(o); + + storageManager.shutdown(); + } + + /** + * Tests data types without storing data in them. e.g. new ArrayList<>() + * + * @param type Injected by JunitFramework + */ + @ParameterizedTest + @EnumSource(TypeEnum.class) + public void saveAndLoadEmptyClassTest(TypeEnum type) + { + + EmbeddedStorageManager storageManager = EmbeddedStorage.start(type.createEmptyInstance(), storagePath); + storageManager.shutdown(); + + storageManager = EmbeddedStorage.start(type.createEmptyInstance(), storagePath); + + storageManager.shutdown(); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerTestData.java b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerTestData.java new file mode 100644 index 000000000..9801c54b3 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/BinaryHandlerTestData.java @@ -0,0 +1,21 @@ +package test.eclipse.store.handler; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public interface BinaryHandlerTestData +{ + + void fillSampleData(); +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/BasicNonPrimitive.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BasicNonPrimitive.java new file mode 100644 index 000000000..d3cd132c7 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BasicNonPrimitive.java @@ -0,0 +1,100 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import test.eclipse.store.handler.BinaryHandlerTestData; + +public class BasicNonPrimitive implements BinaryHandlerTestData +{ + private static final Byte SAMPLE_BYTE = 100; + private static final Short SAMPLE_SHORT = 50; + private static final Integer SAMPLE_INT = 5401; + private static final Long SAMPLE_LONG = 1545455464654L; + private static final Float SAMPLE_FLOAT = 3.141526f; + private static final Double SAMPLE_DOUBLE = 3.141526545; + private static final Boolean SAMPLE_BOOLEAN = Boolean.TRUE; + private static final Character SAMPLE_CHARACTER = 'c'; + private static final String SAMPLE_STRING = "sample string value \t \n"; + + private Byte byteValue; + private Short shortValue; + private Integer intValue; + private Long longValue; + private Float floatValue; + private Double doubleValue; + private Boolean booleanValue; + private Character characterValue; + private String stringValue; + + @Override + public void fillSampleData() + { + byteValue = SAMPLE_BYTE; + shortValue = SAMPLE_SHORT; + intValue = SAMPLE_INT; + longValue = SAMPLE_LONG; + floatValue = SAMPLE_FLOAT; + doubleValue = SAMPLE_DOUBLE; + booleanValue = SAMPLE_BOOLEAN; + characterValue = SAMPLE_CHARACTER; + stringValue = SAMPLE_STRING; + } + + public Byte getByteValue() + { + return byteValue; + } + + public Short getShortValue() + { + return shortValue; + } + + public Integer getIntValue() + { + return intValue; + } + + public Long getLongValue() + { + return longValue; + } + + public Float getFloatValue() + { + return floatValue; + } + + public Double getDoubleValue() + { + return doubleValue; + } + + public Boolean getBooleanValue() + { + return booleanValue; + } + + public Character getCharacterValue() + { + return characterValue; + } + + public String getStringValue() + { + return stringValue; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/BasicNonPrimitiveArrayTypes.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BasicNonPrimitiveArrayTypes.java new file mode 100644 index 000000000..e76a78e66 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BasicNonPrimitiveArrayTypes.java @@ -0,0 +1,191 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import test.eclipse.store.handler.BinaryHandlerTestData; + +public class BasicNonPrimitiveArrayTypes implements BinaryHandlerTestData +{ + private static final Byte SAMPLE_BYTE = 100; + private static final Byte SAMPLE_BYTE_2 = 127; + private static final Short SAMPLE_SHORT = 50; + private static final Short SAMPLE_SHORT_2 = 100; + private static final Integer SAMPLE_INT = 5401; + private static final Integer SAMPLE_INT_2 = 4401; + private static final Long SAMPLE_LONG = 1545455464654L; + private static final Long SAMPLE_LONG_2 = 25546455464654L; + private static final Float SAMPLE_FLOAT = 3.141526f; + private static final Float SAMPLE_FLOAT_2 = 2.141526f; + private static final Double SAMPLE_DOUBLE = 3.141526545; + private static final Double SAMPLE_DOUBLE_2 = 5.141526545; + private static final Character SAMPLE_CHAR = 'c'; + private static final Character SAMPLE_CHAR_2 = 'x'; + + private Byte[] byteValues; + private Short[] shortValues; + private Integer[] intValues; + private Long[] longValues; + private Float[] floatValues; + private Double[] doubleValues; + private Boolean[] booleanValues; + private Character[] charValues; + private Object[] objectValues; + + private Byte[][] byteValueMatrix; + private Short[][] shortValueMatrix; + private Integer[][] intValueMatrix; + private Long[][] longValueMatrix; + private Float[][] floatValueMatrix; + private Double[][] doubleValueMatrix; + private Boolean[][] booleanValueMatrix; + private Character[][] charValueMatrix; + private Object[][] objectValueMatrix; + + public BasicNonPrimitiveArrayTypes() + { + byteValues = new Byte[1]; + shortValues = new Short[1]; + intValues = new Integer[1]; + longValues = new Long[1]; + floatValues = new Float[1]; + doubleValues = new Double[1]; + booleanValues = new Boolean[1]; + charValues = new Character[1]; + + byteValueMatrix = new Byte[1][1]; + shortValueMatrix = new Short[1][1]; + intValueMatrix = new Integer[1][1]; + longValueMatrix = new Long[1][1]; + floatValueMatrix = new Float[1][1]; + doubleValueMatrix = new Double[1][1]; + booleanValueMatrix = new Boolean[1][1]; + charValueMatrix = new Character[1][1]; + } + + @Override + public void fillSampleData() + { + byteValues = new Byte[]{SAMPLE_BYTE, SAMPLE_BYTE_2}; + shortValues = new Short[]{SAMPLE_SHORT, SAMPLE_SHORT_2}; + intValues = new Integer[]{SAMPLE_INT, SAMPLE_INT_2}; + longValues = new Long[]{SAMPLE_LONG, SAMPLE_LONG_2}; + floatValues = new Float[]{SAMPLE_FLOAT, SAMPLE_FLOAT_2}; + doubleValues = new Double[]{SAMPLE_DOUBLE, SAMPLE_DOUBLE_2}; + booleanValues = new Boolean[]{Boolean.TRUE, Boolean.FALSE}; + charValues = new Character[]{SAMPLE_CHAR, SAMPLE_CHAR_2}; + objectValues = new Object[]{SAMPLE_INT, SAMPLE_INT_2}; + + byteValueMatrix = new Byte[][]{{SAMPLE_BYTE, SAMPLE_BYTE_2}, {SAMPLE_BYTE, SAMPLE_BYTE_2}}; + shortValueMatrix = new Short[][]{{SAMPLE_SHORT, SAMPLE_SHORT_2}, {SAMPLE_SHORT, SAMPLE_SHORT_2}}; + intValueMatrix = new Integer[][]{{SAMPLE_INT, SAMPLE_INT_2}, {SAMPLE_INT, SAMPLE_INT_2}}; + longValueMatrix = new Long[][]{{SAMPLE_LONG, SAMPLE_LONG_2}, {SAMPLE_LONG, SAMPLE_LONG_2}}; + floatValueMatrix = new Float[][]{{SAMPLE_FLOAT, SAMPLE_FLOAT_2}, {SAMPLE_FLOAT, SAMPLE_FLOAT_2}}; + doubleValueMatrix = new Double[][]{{SAMPLE_DOUBLE, SAMPLE_DOUBLE_2}, {SAMPLE_DOUBLE, SAMPLE_DOUBLE_2}}; + booleanValueMatrix = new Boolean[][]{{Boolean.TRUE, Boolean.FALSE}, {Boolean.TRUE, Boolean.FALSE}}; + charValueMatrix = new Character[][]{{SAMPLE_CHAR, SAMPLE_CHAR_2}, {SAMPLE_CHAR, SAMPLE_CHAR_2}}; + objectValueMatrix = new Object[][]{{SAMPLE_INT, SAMPLE_INT_2}, {SAMPLE_INT, SAMPLE_INT_2}}; + } + + + public Byte[] getByteValues() + { + return byteValues; + } + + public Short[] getShortValues() + { + return shortValues; + } + + public Integer[] getIntValues() + { + return intValues; + } + + public Long[] getLongValues() + { + return longValues; + } + + public Float[] getFloatValues() + { + return floatValues; + } + + public Double[] getDoubleValues() + { + return doubleValues; + } + + public Boolean[] getBooleanValues() + { + return booleanValues; + } + + public Character[] getCharValues() + { + return charValues; + } + + public Byte[][] getByteValueMatrix() + { + return byteValueMatrix; + } + + public Short[][] getShortValueMatrix() + { + return shortValueMatrix; + } + + public Integer[][] getIntValueMatrix() + { + return intValueMatrix; + } + + public Long[][] getLongValueMatrix() + { + return longValueMatrix; + } + + public Float[][] getFloatValueMatrix() + { + return floatValueMatrix; + } + + public Double[][] getDoubleValueMatrix() + { + return doubleValueMatrix; + } + + public Boolean[][] getBooleanValueMatrix() + { + return booleanValueMatrix; + } + + public Character[][] getCharValueMatrix() + { + return charValueMatrix; + } + + public Object[] getObjectValues() + { + return objectValues; + } + + public Object[][] getObjectValueMatrix() + { + return objectValueMatrix; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/BonusMonat.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BonusMonat.java new file mode 100644 index 000000000..7e0ba9eac --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BonusMonat.java @@ -0,0 +1,221 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.text.DateFormatSymbols; + +public enum BonusMonat +{ + // (20.02.2012 TM)NOTE: Monatsstrings sollten mal dynamisch wartbar konsolidiert werden. + Januar("Jan", 1, 12, "Januar"), + Februar("Feb", 2, 1, "Februar"), + Maerz("Mrz", 3, 2, "März"), // verdammte Sondefälle @ Kürzel ^^ + April("Apr", 4, 3, "April"), + Mai("Mai", 5, 4, "Mai"), + Juni("Jun", 6, 5, "Juni"), + Juli("Jul", 7, 6, "Juli"), + August("Aug", 8, 7, "August"), + September("Sep", 9, 8, "September"), + Oktober("Okt", 10, 9, "Oktober"), + November("Nov", 11, 10, "November"), + Dezember("Dez", 12, 11, "Dezember"); + + + /////////////////////////////////////////////////////////////////////////// + // static methods // + + /// //////////////// + + public static final int orderByKonditionsindex(final BonusMonat n1, final BonusMonat m2) + { + // null Sonderfälle + if (n1 == null) { + return m2 == null ? 0 : -1; + } + if (m2 == null) { + return 1; + } + + // Normalfälle (mal in lustiger kompakter Schreibweise: Fälle definieren, Werte anhängen) + return m2.konditionsindex >= n1.konditionsindex ? m2.konditionsindex != n1.konditionsindex ? -1 : 0 : 1; + } + + + public static BonusMonat fromKalenderindex(final int kalenderindex) + { + // CHECKSTYLE.OFF: MagicNumber: Blanke Indexwerte + switch (kalenderindex) { + case 1: + return Januar; + case 2: + return Februar; + case 3: + return Maerz; + case 4: + return April; + case 5: + return Mai; + case 6: + return Juni; + case 7: + return Juli; + case 8: + return August; + case 9: + return September; + case 10: + return Oktober; + case 11: + return November; + case 12: + return Dezember; + default: + throw new IllegalArgumentException("Kein Kalendermonat: " + kalenderindex); + } + // CHECKSTYLE.ON: MagicNumber + } + + public static BonusMonat fromKonditionsindex(final int abrechnungsindex) + { + // CHECKSTYLE.OFF: MagicNumber: Blanke Indexwerte + switch (abrechnungsindex) { + case 1: + return Februar; + case 2: + return Maerz; + case 3: + return April; + case 4: + return Mai; + case 5: + return Juni; + case 6: + return Juli; + case 7: + return August; + case 8: + return September; + case 9: + return Oktober; + case 10: + return November; + case 11: + return Dezember; + case 12: + return Januar; + default: + throw new IllegalArgumentException("Kein Konditionsmonat: " + abrechnungsindex); + } + // CHECKSTYLE.ON: MagicNumber + } + + + /////////////////////////////////////////////////////////////////////////// + // instance fields // + /// ///////////////// + + private final String kuerzel; + private final int kalenderindex; + private final int konditionsindex; + private final BonusRegulierung first; + private final BonusRegulierung last; + private final String description; + + + /////////////////////////////////////////////////////////////////////////// + // constructors // + + /// ////////////// + + private BonusMonat(final String kuerzel, final int kalenderindex, final int abrechnungsindex, final String description) + { + this.kuerzel = kuerzel; + this.kalenderindex = kalenderindex; + this.konditionsindex = abrechnungsindex; + this.first = BonusRegulierung.fromNummer(2 * abrechnungsindex - 1); + this.last = BonusRegulierung.fromNummer(2 * abrechnungsindex); + this.description = description; + } + + + /////////////////////////////////////////////////////////////////////////// + // getters // + + /// ////////////////// + + public BonusMonat prev() + { + if (this == Januar) { + return Dezember; + } + return fromKalenderindex(this.kalenderindex - 1); + } + + public BonusMonat next() + { + if (this == Dezember) { + return Januar; + } + return fromKalenderindex(this.kalenderindex + 1); + } + + /** + * Wird verwendet für Unterordner und für DTA Verwendungszweck + * + * @return dreibuchstabiges Kürzel des Monatsnamens (Jan, Feb, Mrz, usw.) + */ + public String token() + { + return this.kuerzel; + } + + public int kalenderindex() + { + return this.kalenderindex; + } + + public int konditionsindex() + { + return this.konditionsindex; + } + + public BonusRegulierung firstRegulierung() + { + return this.first; + } + + public BonusRegulierung lastRegulierung() + { + return this.last; + } + + public String toLocaleString() + { + final DateFormatSymbols format = DateFormatSymbols.getInstance(); + return format.getMonths()[this.kalenderindex - 1]; // immer schön alles umkopieren für jeden Aufruf @Sun o_0 + } + + public String toShortLocaleString() + { + final DateFormatSymbols format = DateFormatSymbols.getInstance(); + return format.getShortMonths()[this.kalenderindex - 1]; // immer schön alles umkopieren für jeden Aufruf @Sun o_0 + } + + public String description() + { + return this.description; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/BonusRegulierung.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BonusRegulierung.java new file mode 100644 index 000000000..475a2a41b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/BonusRegulierung.java @@ -0,0 +1,252 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public enum BonusRegulierung +{ + Regulierung01(1), + Regulierung02(2), + Regulierung03(3), + Regulierung04(4), + Regulierung05(5), + Regulierung06(6), + Regulierung07(7), + Regulierung08(8), + Regulierung09(9), + Regulierung10(10), + Regulierung11(11), + Regulierung12(12), + Regulierung13(13), + Regulierung14(14), + Regulierung15(15), + Regulierung16(16), + Regulierung17(17), + Regulierung18(18), + Regulierung19(19), + Regulierung20(20), + Regulierung21(21), + Regulierung22(22), + Regulierung23(23), + Regulierung24(24); + + + /////////////////////////////////////////////////////////////////////////// + // static methods // + + /// //////////////// + + public static BonusRegulierung fromNummer(final int nummer) + { + // CHECKSTYLE.OFF: MagicNumber: Blanke Indexwerte. + switch (nummer) { + case 1: + return Regulierung01; + case 2: + return Regulierung02; + case 3: + return Regulierung03; + case 4: + return Regulierung04; + case 5: + return Regulierung05; + case 6: + return Regulierung06; + case 7: + return Regulierung07; + case 8: + return Regulierung08; + case 9: + return Regulierung09; + case 10: + return Regulierung10; + case 11: + return Regulierung11; + case 12: + return Regulierung12; + case 13: + return Regulierung13; + case 14: + return Regulierung14; + case 15: + return Regulierung15; + case 16: + return Regulierung16; + case 17: + return Regulierung17; + case 18: + return Regulierung18; + case 19: + return Regulierung19; + case 20: + return Regulierung20; + case 21: + return Regulierung21; + case 22: + return Regulierung22; + case 23: + return Regulierung23; + case 24: + return Regulierung24; + default: + throw new IllegalArgumentException("Ungültige Regulierung: " + nummer); + } + // CHECKSTYLE.ON: MagicNumber + } + + public static final BonusRegulierung first() + { + return Regulierung01; + } + + public static final BonusRegulierung last() + { + return Regulierung24; + } + + public BonusRegulierung prev() + { + if (this == first()) { + return null; + } + return fromNummer(this.nummer - 1); + } + + public BonusRegulierung next() + { + if (this == last()) { + return null; + } + return fromNummer(this.nummer + 1); + } + + public final boolean isBefore(final BonusRegulierung other) + { + return this.nummer < other.nummer; + } + + public final boolean isAfter(final BonusRegulierung other) + { + return this.nummer > other.nummer; + } + + public final boolean isEqual(final BonusRegulierung other) + { + return this == other; + } + + public static final BonusRegulierung latest(final BonusRegulierung r1, final BonusRegulierung r2) + { + return r1 == null ? r2 : r2 == null ? r1 : r1.isAfter(r2) ? r1 : r2; + } + + public static final BonusRegulierung earliest(final BonusRegulierung r1, final BonusRegulierung r2) + { + return r1 == null ? r2 : r2 == null ? r1 : r1.isBefore(r2) ? r1 : r2; + } + + + /////////////////////////////////////////////////////////////////////////// + // instance fields // + /// ///////////////// + + private final int nummer; + + + /////////////////////////////////////////////////////////////////////////// + // constructors // + + /// ////////////// + + private BonusRegulierung(final int nummer) + { + this.nummer = nummer; + } + + + /////////////////////////////////////////////////////////////////////////// + // declared methods // + + /// ////////////////// + + public int nummer() + { + return this.nummer; + } + + + public BonusMonat monat() + { + // kann nicht als final member gecacht werden, da Monat das seinerseits mit Regulierung tut + switch (this) { + case Regulierung01: + return BonusMonat.Februar; + case Regulierung02: + return BonusMonat.Februar; + case Regulierung03: + return BonusMonat.Maerz; + case Regulierung04: + return BonusMonat.Maerz; + case Regulierung05: + return BonusMonat.April; + case Regulierung06: + return BonusMonat.April; + case Regulierung07: + return BonusMonat.Mai; + case Regulierung08: + return BonusMonat.Mai; + case Regulierung09: + return BonusMonat.Juni; + case Regulierung10: + return BonusMonat.Juni; + case Regulierung11: + return BonusMonat.Juli; + case Regulierung12: + return BonusMonat.Juli; + case Regulierung13: + return BonusMonat.August; + case Regulierung14: + return BonusMonat.August; + case Regulierung15: + return BonusMonat.September; + case Regulierung16: + return BonusMonat.September; + case Regulierung17: + return BonusMonat.Oktober; + case Regulierung18: + return BonusMonat.Oktober; + case Regulierung19: + return BonusMonat.November; + case Regulierung20: + return BonusMonat.November; + case Regulierung21: + return BonusMonat.Dezember; + case Regulierung22: + return BonusMonat.Dezember; + case Regulierung23: + return BonusMonat.Januar; + case Regulierung24: + return BonusMonat.Januar; + default: + throw new UnsupportedOperationException("Implementierungsfehler für " + this); + } + } + + @Override + public String toString() + { + return BonusRegulierung.class.getSimpleName() + " " + this.nummer; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/CheckService.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/CheckService.java new file mode 100644 index 000000000..100c2f96e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/CheckService.java @@ -0,0 +1,23 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public interface CheckService +{ + Integer DEFAULT_VALUE = 5; + + Integer getValue(); + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/CheckServiceImpl.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/CheckServiceImpl.java new file mode 100644 index 000000000..84ac8f330 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/CheckServiceImpl.java @@ -0,0 +1,26 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class CheckServiceImpl implements CheckService +{ + + @Override + public Integer getValue() + { + return 5; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/PrimitiveArrayTypes.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/PrimitiveArrayTypes.java new file mode 100644 index 000000000..c71cc1977 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/PrimitiveArrayTypes.java @@ -0,0 +1,186 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import test.eclipse.store.handler.BinaryHandlerTestData; + +public class PrimitiveArrayTypes implements BinaryHandlerTestData +{ + private static final byte SAMPLE_BYTE = 100; + private static final byte SAMPLE_BYTE_2 = 127; + private static final short SAMPLE_SHORT = 50; + private static final short SAMPLE_SHORT_2 = 100; + private static final int SAMPLE_INT = 5401; + private static final int SAMPLE_INT_2 = 4401; + private static final long SAMPLE_LONG = 1545455464654L; + private static final long SAMPLE_LONG_2 = 25546455464654L; + private static final float SAMPLE_FLOAT = 3.141526f; + private static final float SAMPLE_FLOAT_2 = 2.141526f; + private static final double SAMPLE_DOUBLE = 3.141526545; + private static final double SAMPLE_DOUBLE_2 = 5.141526545; + private static final char SAMPLE_CHAR = 'c'; + private static final char SAMPLE_CHAR_2 = 'x'; + + private byte[] byteValues; + private short[] shortValues; + private int[] intValues; + private long[] longValues; + private float[] floatValues; + private double[] doubleValues; + private boolean[] booleanValues; + private char[] charValues; + private char[] charEmptyValues; + + private byte[][] byteValueMatrix; + private short[][] shortValueMatrix; + private int[][] intValueMatrix; + private long[][] longValueMatrix; + private float[][] floatValueMatrix; + private double[][] doubleValueMatrix; + private boolean[][] booleanValueMatrix; + private char[][] charValueMatrix; + private char[][] charEmptyMatrix; + + + public static PrimitiveArrayTypes createEmpty() + { + PrimitiveArrayTypes a = new PrimitiveArrayTypes(); + a.byteValues = new byte[1]; + a.shortValues = new short[1]; + a.intValues = new int[1]; + a.longValues = new long[1]; + a.floatValues = new float[1]; + a.doubleValues = new double[1]; + a.booleanValues = new boolean[1]; + a.charValues = new char[1]; + a.charEmptyValues = new char[0]; + + a.byteValueMatrix = new byte[1][1]; + a.shortValueMatrix = new short[1][1]; + a.intValueMatrix = new int[1][1]; + a.longValueMatrix = new long[1][1]; + a.floatValueMatrix = new float[1][1]; + a.doubleValueMatrix = new double[1][1]; + a.booleanValueMatrix = new boolean[1][1]; + a.charValueMatrix = new char[1][1]; + a.charEmptyMatrix = new char[0][0]; + return a; + } + + @Override + public void fillSampleData() + { + byteValues = new byte[]{SAMPLE_BYTE, SAMPLE_BYTE_2}; + shortValues = new short[]{SAMPLE_SHORT, SAMPLE_SHORT_2}; + intValues = new int[]{SAMPLE_INT, SAMPLE_INT_2}; + longValues = new long[]{SAMPLE_LONG, SAMPLE_LONG_2}; + floatValues = new float[]{SAMPLE_FLOAT, SAMPLE_FLOAT_2}; + doubleValues = new double[]{SAMPLE_DOUBLE, SAMPLE_DOUBLE_2}; + booleanValues = new boolean[]{Boolean.TRUE, Boolean.FALSE}; + charValues = new char[]{SAMPLE_CHAR, SAMPLE_CHAR_2}; + charEmptyValues = new char[0]; + + byteValueMatrix = new byte[][]{{SAMPLE_BYTE, SAMPLE_BYTE_2}, {SAMPLE_BYTE, SAMPLE_BYTE_2}}; + shortValueMatrix = new short[][]{{SAMPLE_SHORT, SAMPLE_SHORT_2}, {SAMPLE_SHORT, SAMPLE_SHORT_2}}; + intValueMatrix = new int[][]{{SAMPLE_INT, SAMPLE_INT_2}, {SAMPLE_INT, SAMPLE_INT_2}}; + longValueMatrix = new long[][]{{SAMPLE_LONG, SAMPLE_LONG_2}, {SAMPLE_LONG, SAMPLE_LONG_2}}; + floatValueMatrix = new float[][]{{SAMPLE_FLOAT, SAMPLE_FLOAT_2}, {SAMPLE_FLOAT, SAMPLE_FLOAT_2}}; + doubleValueMatrix = new double[][]{{SAMPLE_DOUBLE, SAMPLE_DOUBLE_2}, {SAMPLE_DOUBLE, SAMPLE_DOUBLE_2}}; + booleanValueMatrix = new boolean[][]{{Boolean.TRUE, Boolean.FALSE}, {Boolean.TRUE, Boolean.FALSE}}; + charValueMatrix = new char[][]{{SAMPLE_CHAR, SAMPLE_CHAR_2}, {SAMPLE_CHAR, SAMPLE_CHAR_2}}; + charEmptyMatrix = new char[0][0]; + } + + public byte[] getByteValues() + { + return byteValues; + } + + public short[] getShortValues() + { + return shortValues; + } + + public int[] getIntValues() + { + return intValues; + } + + public long[] getLongValues() + { + return longValues; + } + + public float[] getFloatValues() + { + return floatValues; + } + + public double[] getDoubleValues() + { + return doubleValues; + } + + public boolean[] getBooleanValues() + { + return booleanValues; + } + + public char[] getCharValues() + { + return charValues; + } + + public byte[][] getByteValueMatrix() + { + return byteValueMatrix; + } + + public short[][] getShortValueMatrix() + { + return shortValueMatrix; + } + + public int[][] getIntValueMatrix() + { + return intValueMatrix; + } + + public long[][] getLongValueMatrix() + { + return longValueMatrix; + } + + public float[][] getFloatValueMatrix() + { + return floatValueMatrix; + } + + public double[][] getDoubleValueMatrix() + { + return doubleValueMatrix; + } + + public boolean[][] getBooleanValueMatrix() + { + return booleanValueMatrix; + } + + public char[][] getCharValueMatrix() + { + return charValueMatrix; + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/basic/PrimitiveTypes.java b/integration-tests/src/test/java/test/eclipse/store/handler/basic/PrimitiveTypes.java new file mode 100644 index 000000000..e147b8b91 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/basic/PrimitiveTypes.java @@ -0,0 +1,152 @@ +package test.eclipse.store.handler.basic; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import test.eclipse.store.handler.BinaryHandlerTestData; + +public class PrimitiveTypes implements BinaryHandlerTestData +{ + private static final byte SAMPLE_BYTE = 100; + private static final short SAMPLE_SHORT = 50; + private static final int SAMPLE_INT = 5401; + private static final long SAMPLE_LONG = 1545455464654L; + private static final float SAMPLE_FLOAT = 3.141526f; + private static final double SAMPLE_DOUBLE = 3.141526545; + private static final boolean SAMPLE_BOOLEAN = Boolean.TRUE; + private static final char SAMPLE_CHAR = 'c'; + + + private byte byteValue; + private short shortValue; + private int intValue; + private long longValue; + private float floatValue; + private double doubleValue; + private boolean booleanValue; + private char charValue; + + public PrimitiveTypes() + { + super(); + } + + public static PrimitiveTypes fillSample() + { + PrimitiveTypes p = new PrimitiveTypes(); + p.fillSampleData(); + return p; + } + + @Override + public void fillSampleData() + { + this.byteValue = SAMPLE_BYTE; + this.shortValue = SAMPLE_SHORT; + this.intValue = SAMPLE_INT; + this.longValue = SAMPLE_LONG; + this.floatValue = SAMPLE_FLOAT; + this.doubleValue = SAMPLE_DOUBLE; + this.booleanValue = SAMPLE_BOOLEAN; + this.charValue = SAMPLE_CHAR; + } + + public byte getByteValue() + { + return byteValue; + } + + public short getShortValue() + { + return shortValue; + } + + public int getIntValue() + { + return intValue; + } + + public long getLongValue() + { + return longValue; + } + + public float getFloatValue() + { + return floatValue; + } + + public double getDoubleValue() + { + return doubleValue; + } + + public boolean isBooleanValue() + { + return booleanValue; + } + + public char getCharValue() + { + return charValue; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (booleanValue ? 1231 : 1237); + result = prime * result + byteValue; + result = prime * result + charValue; + long temp; + temp = Double.doubleToLongBits(doubleValue); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Float.floatToIntBits(floatValue); + result = prime * result + intValue; + result = prime * result + (int) (longValue ^ (longValue >>> 32)); + result = prime * result + shortValue; + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PrimitiveTypes other = (PrimitiveTypes) obj; + if (booleanValue != other.booleanValue) + return false; + if (byteValue != other.byteValue) + return false; + if (charValue != other.charValue) + return false; + if (Double.doubleToLongBits(doubleValue) != Double.doubleToLongBits(other.doubleValue)) + return false; + if (Float.floatToIntBits(floatValue) != Float.floatToIntBits(other.floatValue)) + return false; + if (intValue != other.intValue) + return false; + if (longValue != other.longValue) + return false; + return shortValue == other.shortValue; + } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/other/CalendarTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/other/CalendarTest.java new file mode 100644 index 000000000..0f94e0828 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/other/CalendarTest.java @@ -0,0 +1,207 @@ +package test.eclipse.store.handler.other; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.*; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import test.eclipse.store.handler.AbstractHandlerTest; +import test.eclipse.store.handler.BinaryHandlerTestData; + +class CalendarTest extends AbstractHandlerTest +{ + + CalendarTest() + { + super(CalendarData.class); + } + + @Override + public void proveResult(CalendarData original, CalendarData copy) + { + assertAll( + () -> assertEquals(original.getTimeZone(), copy.getTimeZone()), + () -> assertEquals(original.getCalendar(), copy.getCalendar()), + () -> assertEquals(original.getClock(), copy.getClock()), + () -> assertEquals(original.getDuration(), copy.getDuration()), + () -> assertEquals(original.getInstant(), copy.getInstant()), + () -> assertEquals(original.getLocalDate(), copy.getLocalDate()), + () -> assertEquals(original.getLocalDateTime(), copy.getLocalDateTime()), + () -> assertEquals(original.getLocalTime(), copy.getLocalTime()), + () -> assertEquals(original.getMonthDay(), copy.getMonthDay()), + () -> assertEquals(original.getOffsetDateTime(), copy.getOffsetDateTime()), + () -> assertEquals(original.getOffsetTime(), copy.getOffsetTime()), + () -> assertEquals(original.getPeriod(), copy.getPeriod()), + () -> assertEquals(original.getYear(), copy.getYear()), + () -> assertEquals(original.getYearMonth(), copy.getYearMonth()), + () -> assertEquals(original.getZonedDateTime(), copy.getZonedDateTime()), + () -> assertEquals(original.getZoneId(), copy.getZoneId()), + () -> assertEquals(original.getZoneOffset(), copy.getZoneOffset()), + () -> assertEquals(original.getDayOfWeek(), copy.getDayOfWeek()), + () -> assertEquals(original.getMonth(), copy.getMonth()) + ); + } + + public static class CalendarData implements BinaryHandlerTestData + { + + TimeZone timeZone = TimeZone.getDefault(); + Calendar calendar; + Clock clock; + Duration duration; + Instant instant; + LocalDate localDate; + LocalDateTime localDateTime; + LocalTime localTime; + MonthDay monthDay; + OffsetDateTime offsetDateTime; + OffsetTime offsetTime; + Period period; + Year year; + YearMonth yearMonth; + ZonedDateTime zonedDateTime; + ZoneId zoneId; + ZoneOffset zoneOffset; + + DayOfWeek dayOfWeek; + Month month; + + @Override + public void fillSampleData() + { + timeZone = TimeZone.getTimeZone("Europe/Copenhagen"); + calendar = new GregorianCalendar(); + clock = Clock.systemUTC(); + duration = Duration.ofDays(10); + instant = Instant.MAX; + localDate = LocalDate.now(); + localDateTime = LocalDateTime.now(); + localTime = LocalTime.now(); + monthDay = MonthDay.now(); + offsetDateTime = OffsetDateTime.now(); + offsetTime = OffsetTime.now(); + period = Period.ofDays(7); + year = Year.now(); + yearMonth = YearMonth.now(); + zonedDateTime = ZonedDateTime.now(); + zoneId = ZoneId.systemDefault(); + zoneOffset = ZoneOffset.ofHours(5); + dayOfWeek = DayOfWeek.FRIDAY; + month = Month.DECEMBER; + } + + TimeZone getTimeZone() + { + return timeZone; + } + + Calendar getCalendar() + { + return calendar; + } + + Clock getClock() + { + return clock; + } + + Duration getDuration() + { + return duration; + } + + Instant getInstant() + { + return instant; + } + + LocalDate getLocalDate() + { + return localDate; + } + + LocalDateTime getLocalDateTime() + { + return localDateTime; + } + + LocalTime getLocalTime() + { + return localTime; + } + + MonthDay getMonthDay() + { + return monthDay; + } + + OffsetDateTime getOffsetDateTime() + { + return offsetDateTime; + } + + OffsetTime getOffsetTime() + { + return offsetTime; + } + + Period getPeriod() + { + return period; + } + + Year getYear() + { + return year; + } + + YearMonth getYearMonth() + { + return yearMonth; + } + + ZonedDateTime getZonedDateTime() + { + return zonedDateTime; + } + + ZoneId getZoneId() + { + return zoneId; + } + + ZoneOffset getZoneOffset() + { + return zoneOffset; + } + + DayOfWeek getDayOfWeek() + { + return dayOfWeek; + } + + Month getMonth() + { + return month; + } + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/other/ListStoreAllTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/other/ListStoreAllTest.java new file mode 100644 index 000000000..1f7536925 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/other/ListStoreAllTest.java @@ -0,0 +1,118 @@ +package test.eclipse.store.handler.other; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.Serializable; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class ListStoreAllTest +{ + + @TempDir + Path location; + + @Test + public void storeAllItemsTest() + { + DataRoot dataRoot = new DataRoot(); + dataRoot.addContent(new Info("1", "testName", "secondName")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(location)) { + storageManager.setRoot(dataRoot); + storageManager.storeRoot(); + + + int size = dataRoot.getList() + .size(); + // Init 'database' with some new data. + for (int i = size; i < size + 2; i++) { + Info content = new Info(String.valueOf(i), "Test:" + i, "Test"); + dataRoot.addContent(content); + } + + storageManager.store(dataRoot.list); + + for (Info info : dataRoot.list) { + info.setName("Changed name"); + } + storageManager.storeAll(dataRoot.list); + + storageManager.shutdown(); + } + + try (EmbeddedStorageManager storageManager2 = EmbeddedStorage.start(location)) { + dataRoot = (DataRoot) storageManager2.root(); + assertEquals("Changed name", dataRoot.list.get(0).name); + assertEquals(3, dataRoot.list.size()); + } + + } + + + public static class Info + { + String id; + String name; + String surrName; + + public Info(String id, String name, String surrName) + { + this.id = id; + this.name = name; + this.surrName = surrName; + } + + public void setName(String name) + { + this.name = name; + } + } + + public static class DataRoot implements Serializable + { + List list = new ArrayList<>(); + + public DataRoot() + { + super(); + } + + public List getList() + { + return this.list; + } + + public List addContent(final Info content) + { + list.add(content); + return list; + } + + @Override + public String toString() + { + return "Root: " + this.list; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/other/SqlCalendarTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/other/SqlCalendarTest.java new file mode 100644 index 000000000..41b9aed8b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/other/SqlCalendarTest.java @@ -0,0 +1,72 @@ +package test.eclipse.store.handler.other; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; + +import test.eclipse.store.handler.AbstractHandlerTest; +import test.eclipse.store.handler.BinaryHandlerTestData; + +class SqlCalendarTest extends AbstractHandlerTest +{ + + SqlCalendarTest() + { + super(CalendarData.class); + } + + @Override + public void proveResult(CalendarData original, CalendarData copy) + { + assertAll( + () -> assertEquals(original.getSqlTime(), copy.getSqlTime(), "sqlTime"), + () -> assertEquals(original.getSqlTimestamp(), copy.getSqlTimestamp(), "sqlTimestamp"), + () -> assertEquals(original.date, copy.date, "sqlDate") + ); + } + + + public static class CalendarData implements BinaryHandlerTestData + { + + Timestamp sqlTimestamp; + Time sqlTime; + java.sql.Date date; + + @Override + public void fillSampleData() + { + sqlTimestamp = new Timestamp(System.currentTimeMillis()); + sqlTime = new Time(System.currentTimeMillis()); + date = new Date(System.currentTimeMillis()); + } + + + Timestamp getSqlTimestamp() + { + return sqlTimestamp; + } + + Time getSqlTime() + { + return sqlTime; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/AbstractSpecialHandlerTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/AbstractSpecialHandlerTest.java new file mode 100644 index 000000000..c69139921 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/AbstractSpecialHandlerTest.java @@ -0,0 +1,76 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.PriorityQueue; + +import org.eclipse.serializer.persistence.binary.java.util.BinaryHandlerGenericCollection; +import org.eclipse.serializer.persistence.binary.java.util.BinaryHandlerGenericMap; +import org.eclipse.serializer.persistence.binary.java.util.BinaryHandlerGenericQueue; +import org.eclipse.serializer.persistence.binary.java.util.BinaryHandlerGenericSet; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.io.TempDir; + +abstract class AbstractSpecialHandlerTest +{ + + @TempDir + Path tmpDir; + + EmbeddedStorageManager startStorageWithCustomTypeArrayListHandler(ArrayList root) + { + return EmbeddedStorage.Foundation(tmpDir) + .onConnectionFoundation(f -> f.registerCustomTypeHandler( + BinaryHandlerGenericCollection.New(ArrayList.class) + )) + .start(root); + + } + + EmbeddedStorageManager startStorageWithCustomTypeHashSetHandler(HashSet root) + { + return EmbeddedStorage.Foundation(tmpDir) + .onConnectionFoundation(f -> f.registerCustomTypeHandler( + BinaryHandlerGenericSet.New(HashSet.class) + )) + .start(root); + + } + + EmbeddedStorageManager startStorageWithCustomTypeHashMapHandler(HashMap root) + { + return EmbeddedStorage.Foundation(tmpDir) + .onConnectionFoundation(f -> f.registerCustomTypeHandler( + BinaryHandlerGenericMap.New(HashMap.class) + )) + .start(root); + + } + + EmbeddedStorageManager startStorageWithCustomTypeQueueHandler(PriorityQueue root) + { + return EmbeddedStorage.Foundation(tmpDir) + .onConnectionFoundation(f -> f.registerCustomTypeHandler( + BinaryHandlerGenericQueue.New(PriorityQueue.class) + )) + .start(root); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerCopyOnWriteArraySetUpdate.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerCopyOnWriteArraySetUpdate.java new file mode 100644 index 000000000..928c5d0e5 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerCopyOnWriteArraySetUpdate.java @@ -0,0 +1,59 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.nio.file.Path; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerCopyOnWriteArraySetUpdate +{ + + @TempDir + Path tempDir; + + @Test + public void binaryHandlerCopyOnWriteArraySetUpdateTest() + { + + CopyOnWriteArraySet original = new CopyOnWriteArraySet<>(); + original.add(100); + CopyOnWriteArraySet copy = new CopyOnWriteArraySet<>(); + + EmbeddedStorageManager storage = EmbeddedStorage.start(original, tempDir); + + storage.storeRoot(); + storage.shutdown(); + + storage = EmbeddedStorage.start(original, tempDir); + + original.add(200); + storage.storeRoot(); + storage.shutdown(); + + storage = EmbeddedStorage.start(copy, tempDir); + + assertIterableEquals(original, copy); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericCollectionTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericCollectionTest.java new file mode 100644 index 000000000..13fbc1ee8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericCollectionTest.java @@ -0,0 +1,77 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.ArrayList; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +class BinaryHandlerGenericCollectionTest extends AbstractSpecialHandlerTest +{ + + + @Test + void binaryHandlerGenericCollectionTest() + { + ArrayList original = new ArrayList<>(); + original.add(100); + ArrayList copy = new ArrayList<>(); + + EmbeddedStorageManager storage = startStorageWithCustomTypeArrayListHandler(original); + + storage.storeRoot(); + storage.shutdown(); + + storage = startStorageWithCustomTypeArrayListHandler(copy); + + assertIterableEquals(original, copy); + storage.shutdown(); + + } + +// @Test +// void binaryHandlerGenericCollectionExportImportTest() { +// EmbeddedStorageManager storage; +// ArrayList original = new ArrayList<>(); +// original.add(100); +// ArrayList copy = new ArrayList<>(); +// +// storage = startStorageWithCustomTypeArrayListHandler(original); +// +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(tmpDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorageWithCustomTypeArrayListHandler(copy); +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// assertIterableEquals(original, copy); +// storage.shutdown(); +// } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericEnumTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericEnumTest.java new file mode 100644 index 000000000..d511ddbae --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericEnumTest.java @@ -0,0 +1,139 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class BinaryHandlerGenericEnumTest +{ + + private EmbeddedStorageManager storage; + + private final String TEXT = "SomeText"; + + @TempDir + Path workDir; + + @AfterEach + void cleanStorage() throws IOException + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + FileUtils.deleteDirectory(workDir.toFile()); + } + + @Test + void binaryHandlerGenericEnum() + { + + GenericEnumData original = GenericEnumData.THIRD; + original.setOtherValue(TEXT); + GenericEnumData copy = GenericEnumData.THIRD; + saveAndShutdown(original); + + original.setOtherValue("somethingOthers"); + load(copy); + + assertEquals(TEXT, copy.getOtherValue()); + } + +// @Test +// void exportImportTest() { +// GenericEnumData original = GenericEnumData.THIRD; +// original.setOtherValue(TEXT); +// GenericEnumData copy = GenericEnumData.THIRD; +// +// storage = startStorage(original); +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// assertNotNull(workDir); +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(workDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorage(copy); +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// assertEquals(TEXT, copy.getOtherValue()); +// } + + private void saveAndShutdown(GenericEnumData original) + { + storage = startStorage(original); + storage.storeRoot(); + storage.shutdown(); + } + + private void load(GenericEnumData loaded) + { + storage = startStorage(loaded); + } + + private EmbeddedStorageManager startStorage(Object root) + { + return EmbeddedStorage.start(root, workDir); + } + + private enum GenericEnumData + { + FIRST(1), SECOND(2), THIRD(3), FOURTH(4), FIVE(5); + + int value; + String otherValue; + + GenericEnumData(int value) + { + this.value = value; + } + + public String getOtherValue() + { + return otherValue; + } + + public void setOtherValue(String otherValue) + { + this.otherValue = otherValue; + } + + public int getValue() + { + return value; + } + + public void setValue(int value) + { + this.value = value; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericListTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericListTest.java new file mode 100644 index 000000000..b2c07e9f2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericListTest.java @@ -0,0 +1,76 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.ArrayList; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +class BinaryHandlerGenericListTest extends AbstractSpecialHandlerTest +{ + + @Test + void binaryHandlerGenericListTest() + { + ArrayList original = new ArrayList<>(); + original.add(100); + ArrayList copy = new ArrayList<>(); + + EmbeddedStorageManager storage = startStorageWithCustomTypeArrayListHandler(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorageWithCustomTypeArrayListHandler(copy); + + assertIterableEquals(original, copy); + storage.shutdown(); + + } +// +// @Test +// void binaryHandlerGenericListExportImportTest() { +// EmbeddedStorageManager storage; +// ArrayList original = new ArrayList<>(); +// original.add(100); +// ArrayList copy = new ArrayList<>(); +// +// storage = startStorageWithCustomTypeArrayListHandler(original); +// +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(tmpDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorageWithCustomTypeArrayListHandler(copy); +// +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// assertIterableEquals(original, copy); +// storage.shutdown(); +// } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericMapTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericMapTest.java new file mode 100644 index 000000000..8a6bded18 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericMapTest.java @@ -0,0 +1,76 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.HashMap; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +class BinaryHandlerGenericMapTest extends AbstractSpecialHandlerTest +{ + + @Test + void binaryHandlerGenericMapTest() + { + HashMap original = new HashMap<>(); + original.put(1, 100); + HashMap copy = new HashMap<>(); + + EmbeddedStorageManager storage = startStorageWithCustomTypeHashMapHandler(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorageWithCustomTypeHashMapHandler(copy); + + assertIterableEquals(original.entrySet(), copy.entrySet()); + assertIterableEquals(original.values(), copy.values()); + storage.shutdown(); + } + +// @Test +// void binaryHandlerGenericMapExportImportTest() { +// EmbeddedStorageManager storage; +// HashMap original = new HashMap<>(); +// original.put(1, 100); +// HashMap copy = new HashMap<>(); +// +// storage = startStorageWithCustomTypeHashMapHandler(original); +// +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(tmpDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorageWithCustomTypeHashMapHandler(copy); +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// assertIterableEquals(original.entrySet(), copy.entrySet()); +// assertIterableEquals(original.values(), copy.values()); +// storage.shutdown(); +// } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericQueueTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericQueueTest.java new file mode 100644 index 000000000..96e3a8d33 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericQueueTest.java @@ -0,0 +1,74 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.PriorityQueue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +class BinaryHandlerGenericQueueTest extends AbstractSpecialHandlerTest +{ + + @Test + void binaryHandlerGenericQueueTest() + { + PriorityQueue original = new PriorityQueue<>(); + original.add(100); + PriorityQueue copy = new PriorityQueue<>(); + + EmbeddedStorageManager storage = startStorageWithCustomTypeQueueHandler(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorageWithCustomTypeQueueHandler(copy); + + assertIterableEquals(original, copy); + storage.shutdown(); + + } + +// @Test +// void binaryHandlerGenericQueueExportImportTest() { +// EmbeddedStorageManager storage; +// PriorityQueue original = new PriorityQueue<>(); +// original.add(100); +// PriorityQueue copy = new PriorityQueue<>(); +// +// storage = startStorageWithCustomTypeQueueHandler(original); +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(tmpDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorageWithCustomTypeQueueHandler(copy); +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// storage.shutdown(); +// } + + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericSetTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericSetTest.java new file mode 100644 index 000000000..87dbf7b4a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerGenericSetTest.java @@ -0,0 +1,76 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; + +import java.util.HashSet; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +class BinaryHandlerGenericSetTest extends AbstractSpecialHandlerTest +{ + + @Test + void binaryHandlerGenericSetTest() + { + HashSet original = new HashSet<>(); + original.add(100); + HashSet copy = new HashSet<>(); + + + EmbeddedStorageManager storage = startStorageWithCustomTypeHashSetHandler(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorageWithCustomTypeHashSetHandler(copy); + + assertIterableEquals(original, copy); + storage.shutdown(); + + } + +// @Test +// void binaryHandlerGenericSetExportImportTest() { +// EmbeddedStorageManager storage; +// HashSet original = new HashSet<>(); +// original.add(100); +// HashSet copy = new HashSet<>(); +// +// storage = startStorageWithCustomTypeHashSetHandler(original); +// +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(tmpDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorageWithCustomTypeHashSetHandler(copy); +// +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// storage.shutdown(); +// } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerObjectTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerObjectTest.java new file mode 100644 index 000000000..7aca04c2d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerObjectTest.java @@ -0,0 +1,100 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.io.FileUtils; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.handler.basic.PrimitiveTypes; + +class BinaryHandlerObjectTest +{ + + @TempDir + Path workDir; + private EmbeddedStorageManager storage; + + @AfterEach + void cleanStorage() throws IOException + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + FileUtils.deleteDirectory(workDir.toFile()); + } + + @Test + void binaryHandlerObjectTest() + { + Object original = PrimitiveTypes.fillSample(); + Object copy = new PrimitiveTypes(); + + saveAndReload(original, copy); + + assertEquals(original, copy); + } + + +// @Test +// void exportImportTest() { +// Object original = PrimitiveTypes.fillSample(); +// Object copy = new PrimitiveTypes(); +// +// storage = startStorage(original); +// StorageConnection connection = storage.createConnection(); +// String fileSuffix = "bin"; +// assertNotNull(workDir); +// StorageEntityTypeExportStatistics exportResult = connection.exportTypes( +// new StorageEntityTypeExportFileProvider.Default(workDir, fileSuffix), +// typeHandler -> true // export all, customize if necessary +// ); +// XSequence exportFiles = CQL +// .from(exportResult.typeStatistics().values()) +// .project(s -> (new File(s.file().identifier())).toPath()) +// .execute(); +// storage.shutdown(); +// +// storage = startStorage(copy); +// StorageConnection loadConnection = storage.createConnection(); +// +// loadConnection.importFiles(HashEnum.New(exportFiles)); +// assertEquals(original, copy); +// } + + + O saveAndReload(O original, O loaded) + { + storage = startStorage(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorage(loaded); + return loaded; + } + + private EmbeddedStorageManager startStorage(Object root) + { + return EmbeddedStorage.start(root, workDir); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerWeakReferenceTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerWeakReferenceTest.java new file mode 100644 index 000000000..d3161dccc --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/BinaryHandlerWeakReferenceTest.java @@ -0,0 +1,60 @@ +package test.eclipse.store.handler.special; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.lang.ref.WeakReference; +import java.nio.file.Path; + +import org.eclipse.serializer.persistence.exceptions.PersistenceExceptionTypeNotPersistable; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerWeakReferenceTest +{ + + @TempDir + Path workDir; + private EmbeddedStorageManager storage; + + @Test + void binaryHandlerWeakReferenceTest() + { + Integer i = 30; + + WeakReference original = new WeakReference<>(i); + WeakReference copy = new WeakReference(null); + Assertions.assertThrows(PersistenceExceptionTypeNotPersistable.class, () -> saveAndReload(original, copy)); + + } + + O saveAndReload(O original, O loaded) + { + storage = startStorage(original); + storage.storeRoot(); + storage.shutdown(); + + storage = startStorage(loaded); + return loaded; + } + + + private EmbeddedStorageManager startStorage(Object root) + { + return EmbeddedStorage.start(root, workDir); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerConcurrentSkipListMapTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerConcurrentSkipListMapTest.java new file mode 100644 index 000000000..43f11e517 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerConcurrentSkipListMapTest.java @@ -0,0 +1,88 @@ +package test.eclipse.store.handler.special.comparator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.Comparator; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerConcurrentSkipListMapTest +{ + + @TempDir + Path tempDir; + + @Test + void binaryHandlerConcurrentSkipListMapTest() + { + ConcurrentSkipListMap loadedMap = new ConcurrentSkipListMap<>(Comparator.reverseOrder()); + loadedMap.put(1, 1); + loadedMap.put(2, 2); + loadedMap.put(3, 3); + loadedMap.put(4, 4); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedMap, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + ConcurrentSkipListMap root = (ConcurrentSkipListMap) storageManager.root(); + + // Check if the comparator is set + if (root.comparator() == null) { + throw new IllegalStateException("The comparator should not be null."); + } + + + // Check the order of elements + Integer[] expectedKeys = {4, 3, 2, 1}; + Integer[] actualKeys = root.keySet().toArray(new Integer[0]); + for (int i = 0; i < expectedKeys.length; i++) { + if (!expectedKeys[i].equals(actualKeys[i])) { + throw new IllegalStateException("The keys are not in reverse order."); + } + } + + root.put(-1, -1); + root.put(6, 6); + + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + ConcurrentSkipListMap rootLoaded = (ConcurrentSkipListMap) storageManager.root(); + + // Check if the comparator is set + if (rootLoaded.comparator() == null) { + throw new IllegalStateException("The comparator should not be null."); + } + + // Check the order of elements after adding a new element + Integer[] expectedKeysAfterAdd = {6, 4, 3, 2, 1, -1}; + Integer[] actualKeysAfterAdd = rootLoaded.keySet().toArray(new Integer[0]); + for (int i = 0; i < expectedKeysAfterAdd.length; i++) { + if (!expectedKeysAfterAdd[i].equals(actualKeysAfterAdd[i])) { + throw new IllegalStateException("The keys are not in reverse order after adding a new element."); + } + } + } + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerConcurrentSkipListSetTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerConcurrentSkipListSetTest.java new file mode 100644 index 000000000..01643fd18 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerConcurrentSkipListSetTest.java @@ -0,0 +1,82 @@ +package test.eclipse.store.handler.special.comparator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.nio.file.Path; +import java.util.Comparator; +import java.util.concurrent.ConcurrentSkipListSet; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerConcurrentSkipListSetTest +{ + + @TempDir + Path tempDir; + + @Test + void binaryHandlerConcurrentSkipListSetTest() + { + ConcurrentSkipListSet loadedMap = new ConcurrentSkipListSet<>(Comparator.reverseOrder()); + loadedMap.add(1); + loadedMap.add(2); + loadedMap.add(3); + loadedMap.add(4); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedMap, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + ConcurrentSkipListSet root = (ConcurrentSkipListSet) storageManager.root(); + + // Check if the comparator is set + if (root.comparator() == null) { + throw new IllegalStateException("The comparator should not be null."); + } + + + // Check the order of elements + Integer[] expectedKeys = {4, 3, 2, 1}; + Integer[] actualKeys = root.toArray(new Integer[0]); + assertArrayEquals(expectedKeys, actualKeys, "The keys are not in reverse order."); + + root.add(-1); + root.add(6); + + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + ConcurrentSkipListSet rootLoaded = (ConcurrentSkipListSet) storageManager.root(); + + // Check if the comparator is set + if (rootLoaded.comparator() == null) { + throw new IllegalStateException("The comparator should not be null."); + } + + // Check the order of elements after adding a new element + Integer[] expectedKeysAfterAdd = {6, 4, 3, 2, 1, -1}; + Integer[] actualKeysAfterAdd = rootLoaded.toArray(new Integer[0]); + assertArrayEquals(expectedKeysAfterAdd, actualKeysAfterAdd); + } + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerPriorityQueueComparatorTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerPriorityQueueComparatorTest.java new file mode 100644 index 000000000..81542759a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerPriorityQueueComparatorTest.java @@ -0,0 +1,166 @@ +package test.eclipse.store.handler.special.comparator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.*; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerPriorityQueueComparatorTest +{ + + @TempDir + Path tempDir; + + @Test + void binaryHandlerPriorityQueue_IntegerComparatorTest() + { + PriorityQueue queue = new PriorityQueue<>(Comparator.reverseOrder()); + queue.add(5); + queue.add(1); + queue.add(3); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(queue, tempDir)) { + // The queue is stored + } + + PriorityQueue root = new PriorityQueue<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + + Assertions.assertNotNull(root.comparator(), "The default comparator should be exists."); + + root.add(2); + storageManager.storeRoot(); + + while (!root.isEmpty()) { //Remove all from queue, but not save it + root.poll(); + } + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + final PriorityQueue rootLoaded = (PriorityQueue) storageManager.root(); + + Assertions.assertNotNull(rootLoaded.comparator(), "The default comparator should be exists."); + + List expected = Arrays.asList(5, 3, 2, 1); + List actual = new ArrayList<>(); + while (!rootLoaded.isEmpty()) { + actual.add(rootLoaded.poll()); + } + + Assertions.assertIterableEquals(expected, actual, "The elements should be in reverse order due to the comparator."); + } + + + } + + @Test + void binaryHandlerPriorityQueue() + { + PriorityQueue queue = new PriorityQueue<>(); + queue.add(5); + queue.add(1); + queue.add(3); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(queue, tempDir)) { + // The queue is stored + } + + PriorityQueue root = new PriorityQueue<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + + Assertions.assertNull(root.comparator(), "The default comparator should be null."); + + root.add(2); + + List list = root.stream().toList(); + List expected = List.of(1, 2, 3, 5); + Assertions.assertIterableEquals(expected, list); + + } + } + + @Test + void binaryHandlerPriorityQueueTest() + { + PriorityQueue queue = new PriorityQueue<>(); + queue.add("Hello"); + queue.add("Ahoj"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(queue, tempDir)) { + // The queue is stored + } + + PriorityQueue root = new PriorityQueue<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + + Assertions.assertNull(root.comparator(), "The default comparator should be null."); + + root.add("AAzero"); + + Assertions.assertEquals("AAzero", root.peek(), "Without a comparator, the first element should be 'Hello' due to the natural ordering of strings."); + } + } + + @Test + void binaryHandlerPriorityQueue_comparatorString_updateApi_test() + { + PriorityQueue queue = new PriorityQueue<>(new CustomStringComparator()); + queue.add("Hello"); + queue.add("Ahoj"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(queue, tempDir)) { + // The queue is stored + } + + PriorityQueue root = new PriorityQueue<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + + Assertions.assertNotNull(root.comparator(), "The default comparator should be not null."); + + root.add("zero"); + + Assertions.assertEquals("Ahoj", root.peek()); + } + } + + @Test + void binaryHandlerPriorityQueue_comparatorString_test() + { + PriorityQueue queue = new PriorityQueue<>(new CustomStringComparator()); + queue.add("Hello"); + queue.add("Ahoj"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(queue, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + final PriorityQueue root = (PriorityQueue) storageManager.root(); + + Assertions.assertNotNull(root.comparator()); + + root.add("zero"); + + Assertions.assertEquals("Ahoj", root.peek()); + } + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerTreeMapComparatorTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerTreeMapComparatorTest.java new file mode 100644 index 000000000..5381e4cf9 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerTreeMapComparatorTest.java @@ -0,0 +1,63 @@ +package test.eclipse.store.handler.special.comparator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.TreeMap; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerTreeMapComparatorTest +{ + + @TempDir + Path tempDir; + + + @Test + void treeMapComparatorTest() + { + TreeMap reverseMap = new TreeMap<>(Comparator.reverseOrder()); + reverseMap.put("b", 2); + reverseMap.put("a", 1); + reverseMap.put("c", 3); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(reverseMap, tempDir)) { + reverseMap.put("d", -4); + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + TreeMap loadedMap = (TreeMap) storageManager.root(); + + assertNotNull(loadedMap.comparator()); + + List expectedKeys = List.of("d", "c", "b", "a"); + List actualKeys = new ArrayList<>(loadedMap.keySet()); + assertIterableEquals(expectedKeys, actualKeys); + } + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerTreeSetComparatorTest.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerTreeSetComparatorTest.java new file mode 100644 index 000000000..a60c3b5df --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/BinaryHandlerTreeSetComparatorTest.java @@ -0,0 +1,60 @@ +package test.eclipse.store.handler.special.comparator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.TreeSet; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BinaryHandlerTreeSetComparatorTest +{ + + @TempDir + Path tempDir; + + + @Test + void treeMapComparatorTest() + { + TreeSet reverseSet = new TreeSet<>(Comparator.reverseOrder()); + reverseSet.add(2); + reverseSet.add(1); + reverseSet.add(3); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(reverseSet, tempDir)) { + reverseSet.add(-4); + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + TreeSet loadedSet = (TreeSet) storageManager.root(); + + assertNotNull(loadedSet.comparator()); + + List expectedKeys = List.of(3, 2, 1, -4); + assertIterableEquals(expectedKeys, loadedSet); + } + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/CustomStringComparator.java b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/CustomStringComparator.java new file mode 100644 index 000000000..6f328731d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/handler/special/comparator/CustomStringComparator.java @@ -0,0 +1,27 @@ +package test.eclipse.store.handler.special.comparator; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.Comparator; + +public class CustomStringComparator implements Comparator +{ + + @Override + public int compare(String o1, String o2) + { + return o1.toLowerCase().compareTo(o2.toLowerCase()); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/attribute/AttributeBasicTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/attribute/AttributeBasicTest.java new file mode 100644 index 000000000..d52c82893 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/attribute/AttributeBasicTest.java @@ -0,0 +1,90 @@ +package test.eclipse.store.legacy.attribute; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.attribute.data.AttPerson; +import test.eclipse.store.legacy.legacy.attribute.data.AttPerson2; + +class AttributeBasicTest +{ + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void attributeBasicTest() + { + AttPerson person = new AttPerson("Karel", "May", "Brown", "Black"); + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + AttPerson2 person2 = new AttPerson2(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.attribute.data.AttPerson", "test.eclipse.store.legacy.legacy.attribute.data.AttPerson2")))) + .setRoot(person2) + .createEmbeddedStorageManager(); + + + storage.start(); + assertEquals(person2.getAge(), 0); + storage.store(person2); + storage.shutdown(); + person = new AttPerson(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.attribute.data.AttPerson2", "test.eclipse.store.legacy.legacy.attribute.data.AttPerson")))) + .setRoot(person) + .createEmbeddedStorageManager(); + + storage.start(); + + assertEquals(person.findEyeColor(), "Brown"); + assertEquals(person.getAge(), 0); + storage.shutdown(); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/AbstractLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/AbstractLegacyTest.java new file mode 100644 index 000000000..a25feca30 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/AbstractLegacyTest.java @@ -0,0 +1,43 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.io.TempDir; + +abstract class AbstractLegacyTest +{ + + @TempDir + Path location; + + protected String classPackage = "test.eclipse.store.legacy.legacy.cross.data"; + + protected EmbeddedStorageManager startStorage(Object root, String oldClass, String newClass) + { + return EmbeddedStorage + .Foundation(location) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New(KeyValue.New(oldClass, newClass)))) + .setRoot(root) + .start(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossBooleanLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossBooleanLegacyTest.java new file mode 100644 index 000000000..a50214e66 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossBooleanLegacyTest.java @@ -0,0 +1,54 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.BooleanLegacy; +import test.eclipse.store.legacy.legacy.cross.data.BooleanLegacy2; + +class CrossBooleanLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".BooleanLegacy"; + private String newClass = classPackage + ".BooleanLegacy2"; + + @Test + void crossBooleanLegacyTest() + { + + BooleanLegacy booleanLegacy = BooleanLegacy.fillSample(); + EmbeddedStorageManager storage = EmbeddedStorage.start(booleanLegacy, location); + storage.shutdown(); + + BooleanLegacy2 booleanLegacy2 = new BooleanLegacy2(); + storage = startStorage(booleanLegacy2, oldClass, newClass); + storage.store(booleanLegacy2); + assertTrue(booleanLegacy2.getTo_double() > 0); + storage.shutdown(); + + booleanLegacy = new BooleanLegacy(); + storage = startStorage(booleanLegacy, newClass, oldClass); + storage.store(booleanLegacy); + assertTrue(booleanLegacy.isTo_double()); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossByteLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossByteLegacyTest.java new file mode 100644 index 000000000..f506b8fc1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossByteLegacyTest.java @@ -0,0 +1,55 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.ByteLegacy; +import test.eclipse.store.legacy.legacy.cross.data.ByteLegacy2; + +class CrossByteLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".ByteLegacy"; + private String newClass = classPackage + ".ByteLegacy2"; + + @Test + void crossByteLegacyTest() + { + + ByteLegacy byteLegacy = ByteLegacy.fillSample(); + + EmbeddedStorageManager storage = EmbeddedStorage.start(byteLegacy, location); + storage.shutdown(); + + ByteLegacy2 byteLegacy2 = new ByteLegacy2(); + storage = startStorage(byteLegacy2, oldClass, newClass); + storage.store(byteLegacy2); + assertEquals(byteLegacy.getByteTo_double(), byteLegacy2.getByteTo_double()); + storage.shutdown(); + + byteLegacy = new ByteLegacy(); + storage = startStorage(byteLegacy, newClass, oldClass); + storage.store(byteLegacy); + assertEquals(byteLegacy.getByteTo_double(), byteLegacy2.getByteTo_double()); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossCharLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossCharLegacyTest.java new file mode 100644 index 000000000..ff7ff5534 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossCharLegacyTest.java @@ -0,0 +1,54 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.CharLegacy; +import test.eclipse.store.legacy.legacy.cross.data.CharLegacy2; + +class CrossCharLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".CharLegacy"; + private String newClass = classPackage + ".CharLegacy2"; + + @Test + void crossCharLegacyTest() + { + + CharLegacy charLegacy = CharLegacy.fillSample(); + EmbeddedStorageManager storage = EmbeddedStorage.start(charLegacy, location); + storage.shutdown(); + + CharLegacy2 charLegacy2 = new CharLegacy2(); + storage = startStorage(charLegacy2, oldClass, newClass); + storage.store(charLegacy2); + assertEquals(charLegacy.getCharTo_double(), charLegacy2.getCharTo_double()); + storage.shutdown(); + + charLegacy = new CharLegacy(); + storage = startStorage(charLegacy, newClass, oldClass); + storage.store(charLegacy); + assertEquals(charLegacy.getCharTo_double(), charLegacy2.getCharTo_double()); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossDoubleLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossDoubleLegacyTest.java new file mode 100644 index 000000000..58d3095b8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossDoubleLegacyTest.java @@ -0,0 +1,54 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.DoubleLegacy; +import test.eclipse.store.legacy.legacy.cross.data.DoubleLegacy2; + +class CrossDoubleLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".DoubleLegacy"; + private String newClass = classPackage + ".DoubleLegacy2"; + + @Test + void crossDoubleLegacyTest() + { + + DoubleLegacy doubleLegacy = DoubleLegacy.fillSample(); + EmbeddedStorageManager storage = EmbeddedStorage.start(doubleLegacy, location); + storage.shutdown(); + + DoubleLegacy2 doubleLegacy2 = new DoubleLegacy2(); + storage = startStorage(doubleLegacy2, oldClass, newClass); + storage.store(doubleLegacy2); + assertTrue(doubleLegacy2.getTo_double() > 0); + storage.shutdown(); + + doubleLegacy = new DoubleLegacy(); + storage = startStorage(doubleLegacy, newClass, oldClass); + storage.store(doubleLegacy); + assertTrue(doubleLegacy.getTo_double() > 0); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossFloatLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossFloatLegacyTest.java new file mode 100644 index 000000000..33406dc0e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossFloatLegacyTest.java @@ -0,0 +1,54 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.FloatLegacy; +import test.eclipse.store.legacy.legacy.cross.data.FloatLegacy2; + +class CrossFloatLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".FloatLegacy"; + private String newClass = classPackage + ".FloatLegacy2"; + + @Test + void crossFloatLegacyTest() + { + + FloatLegacy floatLegacy = FloatLegacy.fillSample(); + EmbeddedStorageManager storage = EmbeddedStorage.start(floatLegacy, location); + storage.shutdown(); + + FloatLegacy2 floatLegacy2 = new FloatLegacy2(); + storage = startStorage(floatLegacy2, oldClass, newClass); + storage.store(floatLegacy2); + assertTrue(floatLegacy2.getTo_double() > 0); + storage.shutdown(); + + floatLegacy = new FloatLegacy(); + storage = startStorage(floatLegacy, newClass, oldClass); + storage.store(floatLegacy); + assertTrue(floatLegacy.getTo_double() > 0); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossIntLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossIntLegacyTest.java new file mode 100644 index 000000000..a6c19cccf --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossIntLegacyTest.java @@ -0,0 +1,53 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.IntLegacy; +import test.eclipse.store.legacy.legacy.cross.data.IntLegacy2; + +class CrossIntLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".IntLegacy"; + private String newClass = classPackage + ".IntLegacy2"; + + @Test + void crossIntLegacyTest() + { + + IntLegacy intLegacy = IntLegacy.fillSample(); + + EmbeddedStorageManager storage = EmbeddedStorage.start(intLegacy, location); + storage.shutdown(); + + IntLegacy2 intLegacy2 = new IntLegacy2(); + storage = startStorage(intLegacy2, oldClass, newClass); + storage.store(intLegacy2); + assertTrue(intLegacy2.getTo_double() > 0); + storage.shutdown(); + + intLegacy = new IntLegacy(); + storage = startStorage(intLegacy, newClass, oldClass); + storage.store(intLegacy); + assertTrue(intLegacy.getTo_double() > 0); + storage.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossLongLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossLongLegacyTest.java new file mode 100644 index 000000000..6522bd143 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossLongLegacyTest.java @@ -0,0 +1,53 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.LongLegacy; +import test.eclipse.store.legacy.legacy.cross.data.LongLegacy2; + +class CrossLongLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".LongLegacy"; + private String newClass = classPackage + ".LongLegacy2"; + + @Test + void crossLongLegacyTest() + { + + LongLegacy longLegacy = LongLegacy.fillSample(); + EmbeddedStorageManager storage = EmbeddedStorage.start(longLegacy, location); + storage.shutdown(); + + LongLegacy2 longLegacy2 = new LongLegacy2(); + storage = startStorage(longLegacy2, oldClass, newClass); + storage.store(longLegacy2); + assertTrue(longLegacy2.getTo_double() > 0); + storage.shutdown(); + + longLegacy = new LongLegacy(); + storage = startStorage(longLegacy, newClass, oldClass); + storage.store(longLegacy); + assertTrue(longLegacy.getTo_double() > 0); + storage.shutdown(); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossShortLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossShortLegacyTest.java new file mode 100644 index 000000000..f4338a45a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/cross/CrossShortLegacyTest.java @@ -0,0 +1,53 @@ +package test.eclipse.store.legacy.cross; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; + +import test.eclipse.store.legacy.legacy.cross.data.ShortLegacy; +import test.eclipse.store.legacy.legacy.cross.data.ShortLegacy2; + +class CrossShortLegacyTest extends AbstractLegacyTest +{ + + private String oldClass = classPackage + ".ShortLegacy"; + private String newClass = classPackage + ".ShortLegacy2"; + + @Test + void crossShortLegacyTest() + { + + ShortLegacy shortLegacy = ShortLegacy.fillSample(); + EmbeddedStorageManager storage = EmbeddedStorage.start(shortLegacy, location); + storage.shutdown(); + + ShortLegacy2 shortLegacy2 = new ShortLegacy2(); + storage = startStorage(shortLegacy2, oldClass, newClass); + storage.store(shortLegacy2); + assertTrue(shortLegacy2.getTo_double() > 0); + storage.shutdown(); + + shortLegacy = new ShortLegacy(); + storage = startStorage(shortLegacy, newClass, oldClass); + storage.store(shortLegacy); + assertTrue(shortLegacy.getTo_double() > 0); + storage.shutdown(); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/csv/CsvLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/CsvLegacyTest.java new file mode 100644 index 000000000..f46665d10 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/CsvLegacyTest.java @@ -0,0 +1,128 @@ +package test.eclipse.store.legacy.csv; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; + +import org.eclipse.serializer.persistence.types.Persistence; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.csv.data.CsvPerson; +import test.eclipse.store.legacy.csv.data.CsvPerson2; +import test.eclipse.store.legacy.csv.data.DeletePerson; +import test.eclipse.store.legacy.csv.data.DeletePerson2; + +class CsvLegacyTest +{ + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void legacyDirectTest() throws URISyntaxException + { + CsvPerson person = new CsvPerson("Karel", "May", "1001", "blue"); + + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + CsvPerson2 person2 = new CsvPerson2(); + URL url = this.getClass().getClassLoader().getResource("legacy/refactoring.csv"); + assertNotNull(url); + Path path = new File(url.toURI()).toPath(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(Persistence.RefactoringMapping(path)) + .setRoot(person2) + .start(); + + assertEquals(person.getFirstName(), person2.getFirstName()); + assertEquals(person.getOriginal(), person2.getCopy()); + assertNull(person2.getTittle()); + storage.shutdown(); + } + + @Test + void legacyRemoveFieldCSVTest() throws URISyntaxException + { + DeletePerson person = new DeletePerson("Karel", "May", "1001", "blue"); + + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + DeletePerson2 person2 = new DeletePerson2(); + URL url = this.getClass().getClassLoader().getResource("legacy/delete.csv"); + assertNotNull(url); + Path path = new File(url.toURI()).toPath(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(Persistence.RefactoringMapping(path)) + .setRoot(person2) + .start(); + + assertEquals(person.getFirstName(), person2.getFirstName()); + assertEquals(person.getFullName(), person2.getFullName()); + storage.shutdown(); + } + + @Test + void legacyAddFieldCSVTest() throws URISyntaxException + { + DeletePerson2 person = new DeletePerson2("Karel", "May", "1001"); + + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + DeletePerson person2 = new DeletePerson(); + URL url = this.getClass().getClassLoader().getResource("legacy/add.csv"); + assertNotNull(url); + Path path = new File(url.toURI()).toPath(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(Persistence.RefactoringMapping(path)) + .setRoot(person2) + .start(); + + assertEquals(person.getFirstName(), person2.getFirstName()); + assertEquals(person.getFullName(), person2.getFullName()); + storage.shutdown(); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/CsvPerson.java b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/CsvPerson.java new file mode 100644 index 000000000..6e01cce4d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/CsvPerson.java @@ -0,0 +1,114 @@ +package test.eclipse.store.legacy.csv.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class CsvPerson +{ + + private static final String HAIR_COLOR = "hair_color"; + private static final String EYE_COLOR = "eye_color"; + + private String firstName; + private String lastName; + private String original = "original"; + private Map attributes = new HashMap<>(); + private Integer age = null; + + public CsvPerson(String firstName, String lastName, String eyeColor, String hairColor) + { + this.firstName = firstName; + this.lastName = lastName; + this.attributes.put(HAIR_COLOR, hairColor); + this.attributes.put(EYE_COLOR, eyeColor); + } + + public CsvPerson() + { + + } + + public String getOriginal() + { + return original; + } + + public void setOriginal(String original) + { + this.original = original; + } + + public Integer getAge() + { + return age; + } + + public void setAge(Integer age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public String findEyeColor() + { + return attributes.get(EYE_COLOR); + } + + public void setEyeColor(String color) + { + attributes.put(EYE_COLOR, color); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/CsvPerson2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/CsvPerson2.java new file mode 100644 index 000000000..55b4e7ba0 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/CsvPerson2.java @@ -0,0 +1,108 @@ +package test.eclipse.store.legacy.csv.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class CsvPerson2 +{ + + private static final String HAIR_COLOR = "hair_color"; + + private String tittle; + private String firstName; + private String lastName; + private String copy = "copy"; + private Map attributes = new HashMap<>(); + private int age = 2; + + public CsvPerson2() + { + } + + public CsvPerson2(String tittle, String firstName, String lastName, String hairColor) + { + this.tittle = tittle; + this.firstName = firstName; + this.lastName = lastName; + this.attributes.put(HAIR_COLOR, hairColor); + } + + public String getTittle() + { + return tittle; + } + + public String getCopy() + { + return copy; + } + + public void setCopy(String copy) + { + this.copy = copy; + } + + public int getAge() + { + return age; + } + + public void setAge(int age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/DeletePerson.java b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/DeletePerson.java new file mode 100644 index 000000000..456318d48 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/DeletePerson.java @@ -0,0 +1,121 @@ +package test.eclipse.store.legacy.csv.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class DeletePerson +{ + + private static final String HAIR_COLOR = "hair_color"; + private static final String EYE_COLOR = "eye_color"; + + private String firstName; + private String lastName; + private String fullName; + private String original = "original"; + private Map attributes = new HashMap<>(); + private Integer age = null; + + public DeletePerson(String firstName, String lastName, String eyeColor, String hairColor) + { + this.firstName = firstName; + this.lastName = lastName; + this.fullName = firstName + " " + lastName; + this.attributes.put(HAIR_COLOR, hairColor); + this.attributes.put(EYE_COLOR, eyeColor); + } + + public DeletePerson() + { + + } + + public String getFullName() + { + return fullName; + } + + public String getOriginal() + { + return original; + } + + public void setOriginal(String original) + { + this.original = original; + } + + public Integer getAge() + { + return age; + } + + public void setAge(Integer age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public String findEyeColor() + { + return attributes.get(EYE_COLOR); + } + + public void setEyeColor(String color) + { + attributes.put(EYE_COLOR, color); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/DeletePerson2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/DeletePerson2.java new file mode 100644 index 000000000..dd720f701 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/csv/data/DeletePerson2.java @@ -0,0 +1,108 @@ +package test.eclipse.store.legacy.csv.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class DeletePerson2 +{ + + private static final String HAIR_COLOR = "hair_color"; + private static final String EYE_COLOR = "eye_color"; + + private String firstName; + private String fullName; + private String original = "original"; + private Map attributes = new HashMap<>(); + private Integer age = null; + + public DeletePerson2(String firstName, String eyeColor, String hairColor) + { + this.firstName = firstName; + this.attributes.put(HAIR_COLOR, hairColor); + this.attributes.put(EYE_COLOR, eyeColor); + } + + public DeletePerson2() + { + + } + + public String getFullName() + { + return fullName; + } + + public String getOriginal() + { + return original; + } + + public void setOriginal(String original) + { + this.original = original; + } + + public Integer getAge() + { + return age; + } + + public void setAge(Integer age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public String findEyeColor() + { + return attributes.get(EYE_COLOR); + } + + public void setEyeColor(String color) + { + attributes.put(EYE_COLOR, color); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/data/LegacyBasicTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/data/LegacyBasicTest.java new file mode 100644 index 000000000..5b8e10c35 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/data/LegacyBasicTest.java @@ -0,0 +1,71 @@ +package test.eclipse.store.legacy.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.basic.data.Person; +import test.eclipse.store.legacy.legacy.basic.data.Person2; + +class LegacyBasicTest +{ + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void legacyDirectTest() + { + Person person = new Person("Karel", "May", "1001"); + + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + Person2 person2 = new Person2(); + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.basic.data.Person", "test.eclipse.store.legacy.legacy.basic.data.Person2")))) + .setRoot(person2) + .start(); + + assertEquals(person.getUserCode(), person2.getUserName()); + storage.shutdown(); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/delete/DeleteFieldTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/delete/DeleteFieldTest.java new file mode 100644 index 000000000..b9265bd3c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/delete/DeleteFieldTest.java @@ -0,0 +1,97 @@ +package test.eclipse.store.legacy.delete; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.delete.data.DellPerson; +import test.eclipse.store.legacy.legacy.delete.data.DellPerson2; + +class DeleteFieldTest +{ + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + private static final String FIRST_NAME = "Karel"; + private static final String FULL_NAME = "Karel-May"; + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void removeMemberBasicTest() + { + DellPerson person = new DellPerson(FIRST_NAME, "May", FULL_NAME, "Brown", "Black"); + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + DellPerson2 person2 = new DellPerson2(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.delete.data.DellPerson", "test.eclipse.store.legacy.legacy.delete.data.DellPerson2")))) + .setRoot(person2) + .createEmbeddedStorageManager(); + + + storage.start(); + assertEquals(person2.getAge(), 0); + assertEquals(FIRST_NAME, person2.getFirstName()); + assertEquals(FULL_NAME, person2.getFullName()); + storage.store(person2); + storage.shutdown(); + person = new DellPerson(); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.delete.data.DellPerson2", "test.eclipse.store.legacy.legacy.delete.data.DellPerson")))) + .setRoot(person) + .createEmbeddedStorageManager(); + + storage.start(); + + assertEquals(person.findEyeColor(), "Brown"); + assertEquals(person.getAge(), 0); + assertEquals(FIRST_NAME, person2.getFirstName()); + assertEquals(FULL_NAME, person2.getFullName()); + storage.shutdown(); + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumBehindLazyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumBehindLazyTest.java new file mode 100644 index 000000000..816a88590 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumBehindLazyTest.java @@ -0,0 +1,274 @@ +package test.eclipse.store.legacy.enumeration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; + +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig; + +/** + * Tests verifying that enum values survive a round-trip through Eclipse Store + * when they are held behind a {@link Lazy} reference. + */ +public class EnumBehindLazyTest +{ + // ------------------------------------------------------------------------- + // Single enum constant behind Lazy + // ------------------------------------------------------------------------- + + @Test + void lazyEnum_FIRST_survives_roundtrip(@TempDir Path tempDir) + { + Root root = new Root(Lazy.Reference(EnumerationOrig.FIRST)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + Root loaded = (Root) sm.root(); + assertNotNull(loaded.lazy); + assertEquals(EnumerationOrig.FIRST, loaded.lazy.get()); + } + } + + @Test + void lazyEnum_SECOND_survives_roundtrip(@TempDir Path tempDir) + { + Root root = new Root(Lazy.Reference(EnumerationOrig.SECOND)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + Root loaded = (Root) sm.root(); + assertNotNull(loaded.lazy); + assertEquals(EnumerationOrig.SECOND, loaded.lazy.get()); + } + } + + // ------------------------------------------------------------------------- + // Lazy holding null + // ------------------------------------------------------------------------- + + @Test + void lazyEnum_null_survives_roundtrip(@TempDir Path tempDir) + { + Root root = new Root(Lazy.Reference(null)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + Root loaded = (Root) sm.root(); + assertNotNull(loaded.lazy); + assertNull(loaded.lazy.get()); + } + } + + // ------------------------------------------------------------------------- + // Lazy is unloaded (cleared) before re-loading from storage + // ------------------------------------------------------------------------- + + @Test + void lazyEnum_cleared_then_reloaded_from_storage(@TempDir Path tempDir) + { + Root root = new Root(Lazy.Reference(EnumerationOrig.FIRST)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + // Clear the Lazy reference so that the value must be fetched from disk. + root.lazy.clear(); + assertEquals(EnumerationOrig.FIRST, root.lazy.get()); + } + } + + // ------------------------------------------------------------------------- + // Enum value changed in memory then re-stored + // ------------------------------------------------------------------------- + + @Test + void lazyEnum_value_updated_and_persisted(@TempDir Path tempDir) + { + Root root = new Root(Lazy.Reference(EnumerationOrig.FIRST)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + // Change to SECOND, re-store, then verify reload. + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + Root loaded = (Root) sm.root(); + loaded.lazy = Lazy.Reference(EnumerationOrig.SECOND); + sm.store(loaded); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + Root loaded = (Root) sm.root(); + assertEquals(EnumerationOrig.SECOND, loaded.lazy.get()); + } + } + + // ------------------------------------------------------------------------- + // Root holding two Lazy enum references + // ------------------------------------------------------------------------- + + @Test + void lazyEnum_two_lazy_refs_survive_roundtrip(@TempDir Path tempDir) + { + TwoLazyRoot root = new TwoLazyRoot( + Lazy.Reference(EnumerationOrig.FIRST), + Lazy.Reference(EnumerationOrig.SECOND) + ); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + TwoLazyRoot loaded = (TwoLazyRoot) sm.root(); + assertEquals(EnumerationOrig.FIRST, loaded.first.get()); + assertEquals(EnumerationOrig.SECOND, loaded.second.get()); + } + } + + // ------------------------------------------------------------------------- + // Enum field inside an object that itself is behind Lazy + // ------------------------------------------------------------------------- + + @Test + void lazyWrappedObject_containing_enum_survives_roundtrip(@TempDir Path tempDir) + { + Wrapper wrapper = new Wrapper(EnumerationOrig.FIRST); + WrapperRoot root = new WrapperRoot(Lazy.Reference(wrapper)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + WrapperRoot loaded = (WrapperRoot) sm.root(); + assertNotNull(loaded.lazy); + Wrapper w = loaded.lazy.get(); + assertNotNull(w); + assertEquals(EnumerationOrig.FIRST, w.value); + } + } + + @Test + void lazyWrappedObject_containing_both_enum_constants(@TempDir Path tempDir) + { + Wrapper w1 = new Wrapper(EnumerationOrig.FIRST); + Wrapper w2 = new Wrapper(EnumerationOrig.SECOND); + WrapperRoot root = new WrapperRoot(Lazy.Reference(w1)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + // Overwrite with SECOND and re-persist + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + WrapperRoot loaded = (WrapperRoot) sm.root(); + loaded.lazy = Lazy.Reference(w2); + sm.store(loaded); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + WrapperRoot loaded = (WrapperRoot) sm.root(); + assertEquals(EnumerationOrig.SECOND, loaded.lazy.get().value); + } + } + + // ------------------------------------------------------------------------- + // Enum field metadata preserved after round-trip + // ------------------------------------------------------------------------- + + @Test + void lazyEnum_name_and_ordinal_preserved(@TempDir Path tempDir) + { + Root root = new Root(Lazy.Reference(EnumerationOrig.SECOND)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(root, tempDir)) { + sm.storeRoot(); + } + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(tempDir)) { + Root loaded = (Root) sm.root(); + EnumerationOrig reloaded = loaded.lazy.get(); + assertEquals(EnumerationOrig.SECOND.name(), reloaded.name()); + assertEquals(EnumerationOrig.SECOND.ordinal(), reloaded.ordinal()); + assertEquals(EnumerationOrig.SECOND.getName(), reloaded.getName()); + assertEquals(EnumerationOrig.SECOND.getValue(), reloaded.getValue()); + assertEquals(EnumerationOrig.SECOND.getSecondValue(), reloaded.getSecondValue()); + } + } + + // ------------------------------------------------------------------------- + // Helper data classes + // ------------------------------------------------------------------------- + + static class Root + { + Lazy lazy; + + Root(Lazy lazy) + { + this.lazy = lazy; + } + } + + static class TwoLazyRoot + { + Lazy first; + Lazy second; + + TwoLazyRoot(Lazy first, Lazy second) + { + this.first = first; + this.second = second; + } + } + + static class Wrapper + { + EnumerationOrig value; + + Wrapper(EnumerationOrig value) + { + this.value = value; + } + } + + static class WrapperRoot + { + Lazy lazy; + + WrapperRoot(Lazy lazy) + { + this.lazy = lazy; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumUnderGigamapTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumUnderGigamapTest.java new file mode 100644 index 000000000..428006dcf --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumUnderGigamapTest.java @@ -0,0 +1,208 @@ +package test.eclipse.store.legacy.enumeration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; + +import org.eclipse.store.gigamap.types.GigaMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig; + +public class EnumUnderGigamapTest +{ + // ------------------------------------------------------------------------- + // Basic: both enum constants stored and reloaded + // ------------------------------------------------------------------------- + + @Test + void gigaMap_stores_and_reloads_both_enum_constants(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(EnumerationOrig.FIRST); + gigaMap.add(EnumerationOrig.SECOND); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + // persist implicitly via GigaMap's own storage + } + + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + assertEquals(2, reloaded.size()); + assertEquals(EnumerationOrig.FIRST, reloaded.get(0)); + assertEquals(EnumerationOrig.SECOND, reloaded.get(1)); + } + } + + // ------------------------------------------------------------------------- + // Single element: only FIRST + // ------------------------------------------------------------------------- + + @Test + void gigaMap_single_element_FIRST(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(EnumerationOrig.FIRST); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + } + + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + assertEquals(1, reloaded.size()); + assertEquals(EnumerationOrig.FIRST, reloaded.get(0)); + } + } + + // ------------------------------------------------------------------------- + // Single element: only SECOND + // ------------------------------------------------------------------------- + + @Test + void gigaMap_single_element_SECOND(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(EnumerationOrig.SECOND); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + } + + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + assertEquals(1, reloaded.size()); + assertEquals(EnumerationOrig.SECOND, reloaded.get(0)); + } + } + + // ------------------------------------------------------------------------- + // Enum metadata (name, ordinal, domain fields) preserved after reload + // ------------------------------------------------------------------------- + + @Test + void gigaMap_enum_metadata_preserved_after_reload(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(EnumerationOrig.FIRST); + gigaMap.add(EnumerationOrig.SECOND); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + } + + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + EnumerationOrig first = reloaded.get(0); + EnumerationOrig second = reloaded.get(1); + + assertEquals(EnumerationOrig.FIRST.name(), first.name()); + assertEquals(EnumerationOrig.FIRST.ordinal(), first.ordinal()); + assertEquals(EnumerationOrig.FIRST.getName(), first.getName()); + assertEquals(EnumerationOrig.FIRST.getValue(), first.getValue()); + assertEquals(EnumerationOrig.FIRST.getSecondValue(), first.getSecondValue()); + + assertEquals(EnumerationOrig.SECOND.name(), second.name()); + assertEquals(EnumerationOrig.SECOND.ordinal(), second.ordinal()); + assertEquals(EnumerationOrig.SECOND.getName(), second.getName()); + assertEquals(EnumerationOrig.SECOND.getValue(), second.getValue()); + assertEquals(EnumerationOrig.SECOND.getSecondValue(), second.getSecondValue()); + } + } + + // ------------------------------------------------------------------------- + // Multiple reload cycles + // ------------------------------------------------------------------------- + + @Test + void gigaMap_survives_multiple_reload_cycles(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(EnumerationOrig.FIRST); + gigaMap.add(EnumerationOrig.SECOND); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + } + + for (int cycle = 0; cycle < 3; cycle++) { + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + assertEquals(2, reloaded.size(), "cycle " + cycle); + assertEquals(EnumerationOrig.FIRST, reloaded.get(0)); + assertEquals(EnumerationOrig.SECOND, reloaded.get(1)); + } + } + } + + // ------------------------------------------------------------------------- + // Enum identity: reloaded constant is the same JVM object + // ------------------------------------------------------------------------- + + @Test + void gigaMap_reloaded_enum_is_same_jvm_instance(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(EnumerationOrig.FIRST); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + } + + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + // Enums are singletons; the reloaded reference must be identical. + assertNotNull(reloaded.get(0)); + assertEquals(EnumerationOrig.FIRST, reloaded.get(0)); + } + } + + // ------------------------------------------------------------------------- + // GigaMap containing a wrapper object that holds an enum field + // ------------------------------------------------------------------------- + + @Test + void gigaMap_wrapper_with_enum_field_survives_roundtrip(@TempDir Path tempDir) + { + GigaMap gigaMap = GigaMap.New(); + gigaMap.add(new EnumHolder(EnumerationOrig.FIRST)); + gigaMap.add(new EnumHolder(EnumerationOrig.SECOND)); + + try (EmbeddedStorageManager sm = EmbeddedStorage.start(gigaMap, tempDir)) { + } + + GigaMap reloaded = GigaMap.New(); + try (EmbeddedStorageManager sm = EmbeddedStorage.start(reloaded, tempDir)) { + assertEquals(2, reloaded.size()); + assertEquals(EnumerationOrig.FIRST, reloaded.get(0).value); + assertEquals(EnumerationOrig.SECOND, reloaded.get(1).value); + } + } + + // ------------------------------------------------------------------------- + // Helper + // ------------------------------------------------------------------------- + + static class EnumHolder + { + EnumerationOrig value; + + EnumHolder(EnumerationOrig value) + { + this.value = value; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumerationLTMTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumerationLTMTest.java new file mode 100644 index 000000000..1c9bd2171 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/enumeration/EnumerationLTMTest.java @@ -0,0 +1,162 @@ +package test.eclipse.store.legacy.enumeration; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.exceptions.PersistenceExceptionConsistencyObjectId; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.enumeration.data.EnumerationCopy; +import test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig; + +public class EnumerationLTMTest +{ + + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void removeMemberBasicTest() + { + EnumerationOrig eOrig = EnumerationOrig.FIRST; + + storage = EmbeddedStorage.start(eOrig, tempDir); + storage.shutdown(); + + + storage = EmbeddedStorage.start(eOrig, tempDir); + storage.shutdown(); + + + EnumerationCopy eCopy = EnumerationCopy.SECOND; + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig", "test.eclipse.store.legacy.legacy.enumeration.data.EnumerationCopy")))) + .setRoot(eCopy) + .createEmbeddedStorageManager(); + + + assertThrows(PersistenceExceptionConsistencyObjectId.class, () -> storage.start()); + // TODO could be changed after this issue will be resolved: https://github.com/microstream-one/internal/issues/45 + +// assertEquals( EnumerationOrig.FIRST.getName(), eCopy.getName()); +// assertEquals( EnumerationOrig.FIRST.getValue(), eCopy.getValue()); +// storage.store(eCopy); +// storage.shutdown(); +// eOrig = EnumerationOrig.SECOND; +// +// storage = EmbeddedStorage +// .Foundation(tempDir) +// .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( +// EqHashTable.New( +// KeyValue.New("test.eclipse.store.legacy.legacy.enumeration.data.EnumerationCopy", "test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig")))) +// .setRoot(eOrig) +// .createEmbeddedStorageManager(); +// +// storage.start(); +// +// assertEquals(EnumerationOrig.FIRST.getName(), eOrig.getName()); +// assertEquals(EnumerationOrig.FIRST.getValue(), eOrig.getValue()); +// +// storage.shutdown(); + + } + + // ------------------------------------------------------------------------- + // Same scenario, but enum is wrapped inside a class behind a Lazy reference + // ------------------------------------------------------------------------- + + @Test + void removeMemberBasicTest_lazyWrapped() + { + LazyEnumWrapper origWrapper = new LazyEnumWrapper<>(EnumerationOrig.FIRST); + + storage = EmbeddedStorage.start(origWrapper, tempDir); + storage.shutdown(); + + storage = EmbeddedStorage.start(origWrapper, tempDir); + storage.shutdown(); + + LazyEnumWrapper copyWrapper = new LazyEnumWrapper<>(EnumerationCopy.SECOND); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig", "test.eclipse.store.legacy.legacy.enumeration.data.EnumerationCopy")))) + .setRoot(copyWrapper) + .createEmbeddedStorageManager(); + + storage.start(); + EnumerationCopy eCopy = copyWrapper.lazy.get(); + assertEquals(EnumerationOrig.FIRST.getName(), eCopy.getName()); + assertEquals(EnumerationOrig.FIRST.getValue(), eCopy.getValue()); + storage.store(copyWrapper); + storage.shutdown(); + + LazyEnumWrapper origWrapper2 = new LazyEnumWrapper<>(EnumerationOrig.SECOND); + + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.enumeration.data.EnumerationCopy", "test.eclipse.store.legacy.legacy.enumeration.data.EnumerationOrig")))) + .setRoot(origWrapper2) + .createEmbeddedStorageManager(); + + storage.start(); + EnumerationOrig eOrig = origWrapper2.lazy.get(); + assertEquals(EnumerationOrig.FIRST.getName(), eOrig.getName()); + assertEquals(EnumerationOrig.FIRST.getValue(), eOrig.getValue()); + storage.shutdown(); + } + + static class LazyEnumWrapper + { + Lazy lazy; + + LazyEnumWrapper(E value) + { + this.lazy = Lazy.Reference(value); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/incompatible/IncompatibleBasicTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/incompatible/IncompatibleBasicTest.java new file mode 100644 index 000000000..e60ac9ca2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/incompatible/IncompatibleBasicTest.java @@ -0,0 +1,86 @@ +package test.eclipse.store.legacy.incompatible; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.exceptions.TypeCastException; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.incompatible.data.IncompPerson; +import test.eclipse.store.legacy.legacy.incompatible.data.IncompPerson2; + +class IncompatibleBasicTest +{ + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + private static PrintStream originalErr; + + @BeforeAll + static void setupErr() + { + originalErr = System.err; + System.setErr(new PrintStream(new OutputStream() + { + @Override + public void write(int b) + { + // Discard output + } + })); + } + + @AfterAll + static void restoreErr() + { + System.setErr(originalErr); + } + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void incompatibilityTest() + { + IncompPerson person = new IncompPerson("Karel", "May", "1001"); + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + IncompPerson2 person2 = new IncompPerson2(); + + storage = EmbeddedStorage.Foundation(tempDir).setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New(EqHashTable.New(KeyValue.New("test.eclipse.store.legacy.legacy.incompatible.data.Person", "test.eclipse.store.legacy.legacy.incompatible.data.Person2")))).setRoot(person2).createEmbeddedStorageManager(); + + Assertions.assertThrows(TypeCastException.class, () -> storage.start()); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/attribute/data/AttPerson.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/attribute/data/AttPerson.java new file mode 100644 index 000000000..ada5b766c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/attribute/data/AttPerson.java @@ -0,0 +1,103 @@ +package test.eclipse.store.legacy.legacy.attribute.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class AttPerson +{ + + private static final String HAIR_COLOR = "hair_color"; + private static final String EYE_COLOR = "eye_color"; + + private String firstName; + private String lastName; + private Map attributes = new HashMap<>(); + private Integer age = null; + + public AttPerson(String firstName, String lastName, String eyeColor, String hairColor) + { + this.firstName = firstName; + this.lastName = lastName; + this.attributes.put(HAIR_COLOR, hairColor); + this.attributes.put(EYE_COLOR, eyeColor); + } + + public AttPerson() + { + + } + + public Integer getAge() + { + return age; + } + + public void setAge(Integer age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public String findEyeColor() + { + return attributes.get(EYE_COLOR); + } + + public void setEyeColor(String color) + { + attributes.put(EYE_COLOR, color); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/attribute/data/AttPerson2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/attribute/data/AttPerson2.java new file mode 100644 index 000000000..30d735e03 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/attribute/data/AttPerson2.java @@ -0,0 +1,90 @@ +package test.eclipse.store.legacy.legacy.attribute.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class AttPerson2 +{ + + private static final String HAIR_COLOR = "hair_color"; + + private String firstName; + private String lastName; + private Map attributes = new HashMap<>(); + private int age = 2; + + public AttPerson2() + { + } + + public AttPerson2(String firstName, String lastName, String hairColor) + { + this.firstName = firstName; + this.lastName = lastName; + this.attributes.put(HAIR_COLOR, hairColor); + } + + public int getAge() + { + return age; + } + + public void setAge(int age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/basic/data/Person.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/basic/data/Person.java new file mode 100644 index 000000000..be45718b0 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/basic/data/Person.java @@ -0,0 +1,64 @@ +package test.eclipse.store.legacy.legacy.basic.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class Person +{ + + private String userCode; + private String firstName; + private String secondName; + + public Person() + { + } + + public Person(String firstName, String secondName, String userCode) + { + this.firstName = firstName; + this.secondName = secondName; + this.userCode = userCode; + } + + public String getUserCode() + { + return userCode; + } + + public void setUserCode(String userCode) + { + this.userCode = userCode; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/basic/data/Person2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/basic/data/Person2.java new file mode 100644 index 000000000..7426c3749 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/basic/data/Person2.java @@ -0,0 +1,64 @@ +package test.eclipse.store.legacy.legacy.basic.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class Person2 +{ + + private String firstName; + private String secondName; + private String userName; + + public Person2() + { + } + + public Person2(String firstName, String secondName, String userName) + { + this.firstName = firstName; + this.secondName = secondName; + this.userName = userName; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/BooleanLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/BooleanLegacy.java new file mode 100644 index 000000000..46ea15857 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/BooleanLegacy.java @@ -0,0 +1,158 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class BooleanLegacy +{ + + private boolean to_float; + private boolean to_double; + private boolean to_short; + private boolean to_long; + private boolean to_byte; + private boolean to_int; + private boolean to_boolean; + private boolean to_char; + private boolean toBoolean; + private Boolean copyBooleanTo_boolean; + + public BooleanLegacy() + { + } + + public static BooleanLegacy fillSample() + { + BooleanLegacy legacy = new BooleanLegacy(); + + legacy.to_float = false; + legacy.to_double = true; + legacy.to_short = false; + legacy.to_long = true; + legacy.to_byte = false; + legacy.to_int = true; + legacy.to_boolean = true; + legacy.to_char = false; + legacy.toBoolean = true; + legacy.copyBooleanTo_boolean = true; + return legacy; + } + + public boolean isTo_float() + { + return to_float; + } + + public void setTo_float(boolean to_float) + { + this.to_float = to_float; + } + + public boolean isTo_double() + { + return to_double; + } + + public void setTo_double(boolean to_double) + { + this.to_double = to_double; + } + + public boolean isTo_short() + { + return to_short; + } + + public void setTo_short(boolean to_short) + { + this.to_short = to_short; + } + + public boolean isTo_long() + { + return to_long; + } + + public void setTo_long(boolean to_long) + { + this.to_long = to_long; + } + + public boolean isTo_byte() + { + return to_byte; + } + + public void setTo_byte(boolean to_byte) + { + this.to_byte = to_byte; + } + + public boolean isTo_int() + { + return to_int; + } + + public void setTo_int(boolean to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public boolean isTo_char() + { + return to_char; + } + + public void setTo_char(boolean to_char) + { + this.to_char = to_char; + } + + public boolean isToBoolean() + { + return toBoolean; + } + + public void setToBoolean(boolean toBoolean) + { + this.toBoolean = toBoolean; + } + + @Override + public String toString() + { + return "BooleanLegacy{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toBoolean=" + toBoolean + + ", copyBooleanTo_boolean=" + copyBooleanTo_boolean + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/BooleanLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/BooleanLegacy2.java new file mode 100644 index 000000000..68406edf3 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/BooleanLegacy2.java @@ -0,0 +1,140 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class BooleanLegacy2 +{ + + private float to_float; + private double to_double; + private short to_short; + private long to_long; + private byte to_byte; + private int to_int; + private boolean to_boolean; + private char to_char; + private Boolean toBoolean; + private boolean copyBooleanTo_boolean; + + public BooleanLegacy2() + { + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public byte getTo_byte() + { + return to_byte; + } + + public void setTo_byte(byte to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public char getTo_char() + { + return to_char; + } + + public void setTo_char(char to_char) + { + this.to_char = to_char; + } + + public Boolean getToBoolean() + { + return toBoolean; + } + + public void setToBoolean(Boolean toBoolean) + { + this.toBoolean = toBoolean; + } + + @Override + public String toString() + { + return "BooleanLegacy2{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toBoolean=" + toBoolean + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ByteLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ByteLegacy.java new file mode 100644 index 000000000..d9b9dbc3f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ByteLegacy.java @@ -0,0 +1,76 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ByteLegacy +{ + + private byte byteTo_float; + private byte byteTo_double; + private byte byteTo_short; + private byte byteTo_long; + private byte byteTo_byte; + private byte byteTo_int; + private byte byteToString; + private byte byteTo_boolean; + private byte byteTo_char; + private byte byteToByte; + private Byte copyByteTo_byte; + + public ByteLegacy() + { + } + + public static ByteLegacy fillSample() + { + ByteLegacy legacy = new ByteLegacy(); + + legacy.byteTo_float = '1'; + legacy.byteTo_double = '2'; + legacy.byteTo_short = '3'; + legacy.byteTo_long = '4'; + legacy.byteTo_byte = 'a'; + legacy.byteTo_int = '6'; + legacy.byteToString = '5'; + legacy.byteTo_boolean = '0'; + legacy.byteTo_char = 'c'; + legacy.byteToByte = 'x'; + legacy.copyByteTo_byte = 'i'; + return legacy; + } + + public byte getByteTo_double() + { + return byteTo_double; + } + + @Override + public String toString() + { + return "ByteLegacy{" + + "byteTo_float=" + byteTo_float + + ", byteTo_double=" + byteTo_double + + ", byteTo_short=" + byteTo_short + + ", byteTo_long=" + byteTo_long + + ", byteTo_byte=" + byteTo_byte + + ", byteTo_int=" + byteTo_int + + ", byteToString=" + byteToString + + ", byteTo_boolean=" + byteTo_boolean + + ", byteTo_char=" + byteTo_char + + ", byteToByte=" + byteToByte + + ", ByteTo_byte=" + copyByteTo_byte + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ByteLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ByteLegacy2.java new file mode 100644 index 000000000..3ea4c300f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ByteLegacy2.java @@ -0,0 +1,58 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ByteLegacy2 +{ + + private float byteTo_float; + private double byteTo_double; + private short byteTo_short; + private long byteTo_long; + private byte byteTo_byte; + private int byteTo_int; + private char byteToString; + private boolean byteTo_boolean; + private char byteTo_char; + private Byte bytToByte; + private byte copyByteTo_byte; + + public ByteLegacy2() + { + } + + public double getByteTo_double() + { + return byteTo_double; + } + + @Override + public String toString() + { + return "ByteLegacy2{" + + "byteTo_float=" + byteTo_float + + ", byteTo_double=" + byteTo_double + + ", byteTo_short=" + byteTo_short + + ", byteTo_long=" + byteTo_long + + ", byteTo_byte=" + byteTo_byte + + ", byteTo_int=" + byteTo_int + + ", byteToString=" + byteToString + + ", byteTo_boolean=" + byteTo_boolean + + ", byteTo_char=" + byteTo_char + + ", bytToByte=" + bytToByte + + ", copyByteTo_byte=" + copyByteTo_byte + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/CharLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/CharLegacy.java new file mode 100644 index 000000000..55909090b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/CharLegacy.java @@ -0,0 +1,168 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class CharLegacy +{ + + private char charTo_float; + private char charTo_double; + private char charTo_short; + private char charTo_long; + private char charTo_byte; + private char charTo_int; + private char charToString; + private char charTo_boolean; + private char charTo_char; + private char charToCharacter; + + public CharLegacy() + { + } + + public static CharLegacy fillSample() + { + CharLegacy charLegacy = new CharLegacy(); + + charLegacy.charTo_float = '1'; + charLegacy.charTo_double = '2'; + charLegacy.charTo_short = '3'; + charLegacy.charTo_long = '4'; + charLegacy.charTo_byte = 'č'; + charLegacy.charTo_int = '6'; + charLegacy.charToString = '5'; + charLegacy.charTo_boolean = '0'; + charLegacy.charTo_char = 'c'; + charLegacy.charToCharacter = 'x'; + return charLegacy; + } + + public char getCharTo_float() + { + return charTo_float; + } + + public void setCharTo_float(char charTo_float) + { + this.charTo_float = charTo_float; + } + + public char getCharTo_double() + { + return charTo_double; + } + + public void setCharTo_double(char charTo_double) + { + this.charTo_double = charTo_double; + } + + public char getCharTo_short() + { + return charTo_short; + } + + public void setCharTo_short(char charTo_short) + { + this.charTo_short = charTo_short; + } + + public char getCharTo_long() + { + return charTo_long; + } + + public void setCharTo_long(char charTo_long) + { + this.charTo_long = charTo_long; + } + + public char getCharTo_byte() + { + return charTo_byte; + } + + public void setCharTo_byte(char charTo_byte) + { + this.charTo_byte = charTo_byte; + } + + public char getCharTo_int() + { + return charTo_int; + } + + public void setCharTo_int(char charTo_int) + { + this.charTo_int = charTo_int; + } + + public char getCharToString() + { + return charToString; + } + + public void setCharToString(char charToString) + { + this.charToString = charToString; + } + + public char getCharTo_boolean() + { + return charTo_boolean; + } + + public void setCharTo_boolean(char charTo_boolean) + { + this.charTo_boolean = charTo_boolean; + } + + public char getCharTo_char() + { + return charTo_char; + } + + public void setCharTo_char(char charTo_char) + { + this.charTo_char = charTo_char; + } + + public char getCharToCharacter() + { + return charToCharacter; + } + + public void setCharToCharacter(char charToCharacter) + { + this.charToCharacter = charToCharacter; + } + + @Override + public String toString() + { + return "CharLegacy{" + + "charTo_float=" + charTo_float + + ", charTo_double=" + charTo_double + + ", charTo_short=" + charTo_short + + ", charTo_long=" + charTo_long + + ", charTo_byte=" + charTo_byte + + ", charTo_int=" + charTo_int + + ", charToString=" + charToString + + ", charTo_boolean=" + charTo_boolean + + ", charTo_char=" + charTo_char + + ", charToCharacter=" + charToCharacter + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/CharLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/CharLegacy2.java new file mode 100644 index 000000000..920379f20 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/CharLegacy2.java @@ -0,0 +1,151 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class CharLegacy2 +{ + + private float charTo_float; + private double charTo_double; + private short charTo_short; + private long charTo_long; + private byte charTo_byte; + private int charTo_int; + private char charToString; + private boolean charTo_boolean; + private char charTo_char; + private Character charToCharacter; + + public CharLegacy2() + { + } + + public float getCharTo_float() + { + return charTo_float; + } + + public void setCharTo_float(float charTo_float) + { + this.charTo_float = charTo_float; + } + + public double getCharTo_double() + { + return charTo_double; + } + + public void setCharTo_double(double charTo_double) + { + this.charTo_double = charTo_double; + } + + public short getCharTo_short() + { + return charTo_short; + } + + public void setCharTo_short(short charTo_short) + { + this.charTo_short = charTo_short; + } + + public long getCharTo_long() + { + return charTo_long; + } + + public void setCharTo_long(long charTo_long) + { + this.charTo_long = charTo_long; + } + + public byte getCharTo_byte() + { + return charTo_byte; + } + + public void setCharTo_byte(byte charTo_byte) + { + this.charTo_byte = charTo_byte; + } + + public int getCharTo_int() + { + return charTo_int; + } + + public void setCharTo_int(int charTo_int) + { + this.charTo_int = charTo_int; + } + + public char getCharToString() + { + return charToString; + } + + public void setCharToString(char charToString) + { + this.charToString = charToString; + } + + public boolean isCharTo_boolean() + { + return charTo_boolean; + } + + public void setCharTo_boolean(boolean charTo_boolean) + { + this.charTo_boolean = charTo_boolean; + } + + public char getCharTo_char() + { + return charTo_char; + } + + public void setCharTo_char(char charTo_char) + { + this.charTo_char = charTo_char; + } + + public Character getCharToCharacter() + { + return charToCharacter; + } + + public void setCharToCharacter(Character charToCharacter) + { + this.charToCharacter = charToCharacter; + } + + @Override + public String toString() + { + return "CharLegacy2{" + + "charTo_float=" + charTo_float + + ", charTo_double=" + charTo_double + + ", charTo_short=" + charTo_short + + ", charTo_long=" + charTo_long + + ", charTo_byte=" + charTo_byte + + ", charTo_int=" + charTo_int + + ", charToString=" + charToString + + ", charTo_boolean=" + charTo_boolean + + ", charTo_char=" + charTo_char + + ", charToCharacter=" + charToCharacter + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/DoubleLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/DoubleLegacy.java new file mode 100644 index 000000000..bdd37f048 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/DoubleLegacy.java @@ -0,0 +1,155 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class DoubleLegacy +{ + + private double to_float; + private double to_double; + private double to_short; + private double to_long; + private double to_byte; + private double to_int; + private double to_boolean; + private double to_char; + private double toDouble; + + public DoubleLegacy() + { + } + + public static DoubleLegacy fillSample() + { + DoubleLegacy legacy = new DoubleLegacy(); + + legacy.to_float = 1; + legacy.to_double = 2; + legacy.to_short = 3; + legacy.to_long = 4; + legacy.to_byte = 5; + legacy.to_int = 6; + legacy.to_boolean = 0; + legacy.to_char = 1; + legacy.toDouble = 3; + return legacy; + } + + public double getTo_float() + { + return to_float; + } + + public void setTo_float(double to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public double getTo_short() + { + return to_short; + } + + public void setTo_short(double to_short) + { + this.to_short = to_short; + } + + public double getTo_long() + { + return to_long; + } + + public void setTo_long(double to_long) + { + this.to_long = to_long; + } + + public double getTo_byte() + { + return to_byte; + } + + public void setTo_byte(double to_byte) + { + this.to_byte = to_byte; + } + + public double getTo_int() + { + return to_int; + } + + public void setTo_int(double to_int) + { + this.to_int = to_int; + } + + public double getTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(double to_boolean) + { + this.to_boolean = to_boolean; + } + + public double getTo_char() + { + return to_char; + } + + public void setTo_char(double to_char) + { + this.to_char = to_char; + } + + public double getToDouble() + { + return toDouble; + } + + public void setToDouble(double toDouble) + { + this.toDouble = toDouble; + } + + @Override + public String toString() + { + return "DoubleLegacy{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toDouble=" + toDouble + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/DoubleLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/DoubleLegacy2.java new file mode 100644 index 000000000..159751982 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/DoubleLegacy2.java @@ -0,0 +1,139 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class DoubleLegacy2 +{ + + private float to_float; + private double to_double; + private short to_short; + private long to_long; + private byte to_byte; + private int to_int; + private boolean to_boolean; + private char to_char; + private Double toDouble; + + public DoubleLegacy2() + { + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public byte getTo_byte() + { + return to_byte; + } + + public void setTo_byte(byte to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public char getTo_char() + { + return to_char; + } + + public void setTo_char(char to_char) + { + this.to_char = to_char; + } + + public Double getToDouble() + { + return toDouble; + } + + public void setToDouble(Double toDouble) + { + this.toDouble = toDouble; + } + + @Override + public String toString() + { + return "DoubleLegacy2{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toDouble=" + toDouble + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/FloatLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/FloatLegacy.java new file mode 100644 index 000000000..2731035e1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/FloatLegacy.java @@ -0,0 +1,155 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class FloatLegacy +{ + + private float to_float; + private float to_double; + private float to_short; + private float to_long; + private float to_byte; + private float to_int; + private float to_boolean; + private float to_char; + private float toFloat; + + public FloatLegacy() + { + } + + public static FloatLegacy fillSample() + { + FloatLegacy legacy = new FloatLegacy(); + + legacy.to_float = 1; + legacy.to_double = 2; + legacy.to_short = 3; + legacy.to_long = 4; + legacy.to_byte = 5; + legacy.to_int = 6; + legacy.to_boolean = 0; + legacy.to_char = 1; + legacy.toFloat = 3; + return legacy; + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public float getTo_double() + { + return to_double; + } + + public void setTo_double(float to_double) + { + this.to_double = to_double; + } + + public float getTo_short() + { + return to_short; + } + + public void setTo_short(float to_short) + { + this.to_short = to_short; + } + + public float getTo_long() + { + return to_long; + } + + public void setTo_long(float to_long) + { + this.to_long = to_long; + } + + public float getTo_byte() + { + return to_byte; + } + + public void setTo_byte(float to_byte) + { + this.to_byte = to_byte; + } + + public float getTo_int() + { + return to_int; + } + + public void setTo_int(float to_int) + { + this.to_int = to_int; + } + + public float getTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(float to_boolean) + { + this.to_boolean = to_boolean; + } + + public float getTo_char() + { + return to_char; + } + + public void setTo_char(float to_char) + { + this.to_char = to_char; + } + + public float getToFloat() + { + return toFloat; + } + + public void setToFloat(float toFloat) + { + this.toFloat = toFloat; + } + + @Override + public String toString() + { + return "FloatLegacy{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toFloat=" + toFloat + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/FloatLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/FloatLegacy2.java new file mode 100644 index 000000000..80d7583ac --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/FloatLegacy2.java @@ -0,0 +1,139 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class FloatLegacy2 +{ + + private float to_float; + private double to_double; + private short to_short; + private long to_long; + private byte to_byte; + private int to_int; + private boolean to_boolean; + private char to_char; + private Float toFloat; + + public FloatLegacy2() + { + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public byte getTo_byte() + { + return to_byte; + } + + public void setTo_byte(byte to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public char getTo_char() + { + return to_char; + } + + public void setTo_char(char to_char) + { + this.to_char = to_char; + } + + public Float getToFloat() + { + return toFloat; + } + + public void setToFloat(Float toFloat) + { + this.toFloat = toFloat; + } + + @Override + public String toString() + { + return "FloatLegacy2{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toFloat=" + toFloat + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/IntLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/IntLegacy.java new file mode 100644 index 000000000..fc135527d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/IntLegacy.java @@ -0,0 +1,155 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class IntLegacy +{ + + private int to_float; + private int to_double; + private int to_short; + private int to_long; + private int to_byte; + private int to_int; + private int to_boolean; + private int to_char; + private int toInteger; + + public IntLegacy() + { + } + + public static IntLegacy fillSample() + { + IntLegacy legacy = new IntLegacy(); + + legacy.to_float = 1; + legacy.to_double = 2; + legacy.to_short = 3; + legacy.to_long = 4; + legacy.to_byte = 5; + legacy.to_int = 6; + legacy.to_boolean = 0; + legacy.to_char = 1; + legacy.toInteger = 3; + return legacy; + } + + public int getTo_float() + { + return to_float; + } + + public void setTo_float(int to_float) + { + this.to_float = to_float; + } + + public int getTo_double() + { + return to_double; + } + + public void setTo_double(int to_double) + { + this.to_double = to_double; + } + + public int getTo_short() + { + return to_short; + } + + public void setTo_short(int to_short) + { + this.to_short = to_short; + } + + public int getTo_long() + { + return to_long; + } + + public void setTo_long(int to_long) + { + this.to_long = to_long; + } + + public int getTo_byte() + { + return to_byte; + } + + public void setTo_byte(int to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public int getTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(int to_boolean) + { + this.to_boolean = to_boolean; + } + + public int getTo_char() + { + return to_char; + } + + public void setTo_char(int to_char) + { + this.to_char = to_char; + } + + public int getToInteger() + { + return toInteger; + } + + public void setToInteger(int toInteger) + { + this.toInteger = toInteger; + } + + @Override + public String toString() + { + return "IntLegacy{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toInteger=" + toInteger + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/IntLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/IntLegacy2.java new file mode 100644 index 000000000..e7e0f46e6 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/IntLegacy2.java @@ -0,0 +1,139 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class IntLegacy2 +{ + + private float to_float; + private double to_double; + private short to_short; + private long to_long; + private byte to_byte; + private int to_int; + private boolean to_boolean; + private char to_char; + private Integer toInteger; + + public IntLegacy2() + { + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public byte getTo_byte() + { + return to_byte; + } + + public void setTo_byte(byte to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public char getTo_char() + { + return to_char; + } + + public void setTo_char(char to_char) + { + this.to_char = to_char; + } + + public Integer getToInteger() + { + return toInteger; + } + + public void setToInteger(Integer toInteger) + { + this.toInteger = toInteger; + } + + @Override + public String toString() + { + return "IntLegacy2{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toInteger=" + toInteger + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/LongLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/LongLegacy.java new file mode 100644 index 000000000..adb7eb3bc --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/LongLegacy.java @@ -0,0 +1,155 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class LongLegacy +{ + + private long to_float; + private long to_double; + private long to_short; + private long to_long; + private long to_byte; + private long to_int; + private long to_boolean; + private long to_char; + private long toLong; + + public LongLegacy() + { + } + + public static LongLegacy fillSample() + { + LongLegacy legacy = new LongLegacy(); + + legacy.to_float = 1; + legacy.to_double = 2; + legacy.to_short = 3; + legacy.to_long = 4; + legacy.to_byte = 5; + legacy.to_int = 6; + legacy.to_boolean = 0; + legacy.to_char = 1; + legacy.toLong = 3; + return legacy; + } + + public long getTo_float() + { + return to_float; + } + + public void setTo_float(long to_float) + { + this.to_float = to_float; + } + + public long getTo_double() + { + return to_double; + } + + public void setTo_double(long to_double) + { + this.to_double = to_double; + } + + public long getTo_short() + { + return to_short; + } + + public void setTo_short(long to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public long getTo_byte() + { + return to_byte; + } + + public void setTo_byte(long to_byte) + { + this.to_byte = to_byte; + } + + public long getTo_int() + { + return to_int; + } + + public void setTo_int(long to_int) + { + this.to_int = to_int; + } + + public long getTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(long to_boolean) + { + this.to_boolean = to_boolean; + } + + public long getTo_char() + { + return to_char; + } + + public void setTo_char(long to_char) + { + this.to_char = to_char; + } + + public long getToLong() + { + return toLong; + } + + public void setToLong(long toLong) + { + this.toLong = toLong; + } + + @Override + public String toString() + { + return "LongLegacy{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toLong=" + toLong + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/LongLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/LongLegacy2.java new file mode 100644 index 000000000..f119a78fd --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/LongLegacy2.java @@ -0,0 +1,139 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class LongLegacy2 +{ + + private float to_float; + private double to_double; + private short to_short; + private long to_long; + private byte to_byte; + private int to_int; + private boolean to_boolean; + private char to_char; + private Long toLong; + + public LongLegacy2() + { + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public byte getTo_byte() + { + return to_byte; + } + + public void setTo_byte(byte to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public char getTo_char() + { + return to_char; + } + + public void setTo_char(char to_char) + { + this.to_char = to_char; + } + + public Long getToLong() + { + return toLong; + } + + public void setToLong(Long toLong) + { + this.toLong = toLong; + } + + @Override + public String toString() + { + return "LongLegacy2{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toLong=" + toLong + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ShortLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ShortLegacy.java new file mode 100644 index 000000000..f431f2172 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ShortLegacy.java @@ -0,0 +1,155 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ShortLegacy +{ + + private short to_float; + private short to_double; + private short to_short; + private short to_long; + private short to_byte; + private short to_int; + private short to_boolean; + private short to_char; + private short toShort; + + public ShortLegacy() + { + } + + public static ShortLegacy fillSample() + { + ShortLegacy legacy = new ShortLegacy(); + + legacy.to_float = 1; + legacy.to_double = 2; + legacy.to_short = 3; + legacy.to_long = 4; + legacy.to_byte = 5; + legacy.to_int = 6; + legacy.to_boolean = 0; + legacy.to_char = 1; + legacy.toShort = 3; + return legacy; + } + + public short getTo_float() + { + return to_float; + } + + public void setTo_float(short to_float) + { + this.to_float = to_float; + } + + public short getTo_double() + { + return to_double; + } + + public void setTo_double(short to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public short getTo_long() + { + return to_long; + } + + public void setTo_long(short to_long) + { + this.to_long = to_long; + } + + public short getTo_byte() + { + return to_byte; + } + + public void setTo_byte(short to_byte) + { + this.to_byte = to_byte; + } + + public short getTo_int() + { + return to_int; + } + + public void setTo_int(short to_int) + { + this.to_int = to_int; + } + + public short getTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(short to_boolean) + { + this.to_boolean = to_boolean; + } + + public short getTo_char() + { + return to_char; + } + + public void setTo_char(short to_char) + { + this.to_char = to_char; + } + + public short getToShort() + { + return toShort; + } + + public void setToShort(short toShort) + { + this.toShort = toShort; + } + + @Override + public String toString() + { + return "ShortLegacy{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toShort=" + toShort + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ShortLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ShortLegacy2.java new file mode 100644 index 000000000..5baa9a8f2 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/cross/data/ShortLegacy2.java @@ -0,0 +1,139 @@ +package test.eclipse.store.legacy.legacy.cross.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ShortLegacy2 +{ + + private float to_float; + private double to_double; + private short to_short; + private long to_long; + private byte to_byte; + private int to_int; + private boolean to_boolean; + private char to_char; + private Short toShort; + + public ShortLegacy2() + { + } + + public float getTo_float() + { + return to_float; + } + + public void setTo_float(float to_float) + { + this.to_float = to_float; + } + + public double getTo_double() + { + return to_double; + } + + public void setTo_double(double to_double) + { + this.to_double = to_double; + } + + public short getTo_short() + { + return to_short; + } + + public void setTo_short(short to_short) + { + this.to_short = to_short; + } + + public long getTo_long() + { + return to_long; + } + + public void setTo_long(long to_long) + { + this.to_long = to_long; + } + + public byte getTo_byte() + { + return to_byte; + } + + public void setTo_byte(byte to_byte) + { + this.to_byte = to_byte; + } + + public int getTo_int() + { + return to_int; + } + + public void setTo_int(int to_int) + { + this.to_int = to_int; + } + + public boolean isTo_boolean() + { + return to_boolean; + } + + public void setTo_boolean(boolean to_boolean) + { + this.to_boolean = to_boolean; + } + + public char getTo_char() + { + return to_char; + } + + public void setTo_char(char to_char) + { + this.to_char = to_char; + } + + public Short getToShort() + { + return toShort; + } + + public void setToShort(Short toShort) + { + this.toShort = toShort; + } + + @Override + public String toString() + { + return "ShortLegacy2{" + + "to_float=" + to_float + + ", to_double=" + to_double + + ", to_short=" + to_short + + ", to_long=" + to_long + + ", to_byte=" + to_byte + + ", to_int=" + to_int + + ", to_boolean=" + to_boolean + + ", to_char=" + to_char + + ", toShort=" + toShort + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/delete/data/DellPerson.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/delete/data/DellPerson.java new file mode 100644 index 000000000..901ff9cfb --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/delete/data/DellPerson.java @@ -0,0 +1,115 @@ +package test.eclipse.store.legacy.legacy.delete.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class DellPerson +{ + + private static final String HAIR_COLOR = "hair_color"; + private static final String EYE_COLOR = "eye_color"; + + private String firstName; + private String lastName; + private String fullName; + private Map attributes = new HashMap<>(); + private Integer age = null; + + public DellPerson(String firstName, String lastName, String fullName, String eyeColor, String hairColor) + { + this.firstName = firstName; + this.lastName = lastName; + this.fullName = fullName; + this.attributes.put(HAIR_COLOR, hairColor); + this.attributes.put(EYE_COLOR, eyeColor); + } + + public DellPerson() + { + + } + + public Integer getAge() + { + return age; + } + + public void setAge(Integer age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public String findEyeColor() + { + return attributes.get(EYE_COLOR); + } + + public void setEyeColor(String color) + { + attributes.put(EYE_COLOR, color); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } + + public String getFullName() + { + return fullName; + } + + public void setFullName(String fullName) + { + this.fullName = fullName; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/delete/data/DellPerson2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/delete/data/DellPerson2.java new file mode 100644 index 000000000..95ca82e6f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/delete/data/DellPerson2.java @@ -0,0 +1,90 @@ +package test.eclipse.store.legacy.legacy.delete.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.HashMap; +import java.util.Map; + +public class DellPerson2 +{ + + private static final String HAIR_COLOR = "hair_color"; + + private String firstName; + private String fullName; + private Map attributes = new HashMap<>(); + private int age = 2; + + public DellPerson2() + { + } + + public DellPerson2(String firstName, String hairColor, String fullName) + { + this.firstName = firstName; + this.attributes.put(HAIR_COLOR, hairColor); + this.fullName = fullName; + } + + public int getAge() + { + return age; + } + + public void setAge(int age) + { + this.age = age; + } + + public String findHairColor() + { + return attributes.get(HAIR_COLOR); + } + + public void setHairColor(String color) + { + attributes.put(HAIR_COLOR, color); + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public Map getAttributes() + { + return attributes; + } + + public void setAttributes(Map attributes) + { + this.attributes = attributes; + } + + public String getFullName() + { + return fullName; + } + + public void setFullName(String fullName) + { + this.fullName = fullName; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/enumeration/data/EnumerationCopy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/enumeration/data/EnumerationCopy.java new file mode 100644 index 000000000..cf04a0092 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/enumeration/data/EnumerationCopy.java @@ -0,0 +1,41 @@ +package test.eclipse.store.legacy.legacy.enumeration.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public enum EnumerationCopy +{ + + FIRST("first", "10"), + SECOND("second", "30"); + + private String name; + private String value; + + EnumerationCopy(String name, String value) + { + this.name = name; + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/enumeration/data/EnumerationOrig.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/enumeration/data/EnumerationOrig.java new file mode 100644 index 000000000..81170a289 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/enumeration/data/EnumerationOrig.java @@ -0,0 +1,47 @@ +package test.eclipse.store.legacy.legacy.enumeration.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public enum EnumerationOrig +{ + FIRST("first", "10", "20"), + SECOND("second", "second_value_30", "second_value_40"); + + private String name; + private String value; + private String secondValue; + + EnumerationOrig(String name, String value, String secondValue) + { + this.name = name; + this.value = value; + this.secondValue = secondValue; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + + public String getSecondValue() + { + return secondValue; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/incompatible/data/IncompPerson.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/incompatible/data/IncompPerson.java new file mode 100644 index 000000000..27a74df79 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/incompatible/data/IncompPerson.java @@ -0,0 +1,64 @@ +package test.eclipse.store.legacy.legacy.incompatible.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class IncompPerson +{ + + private String userCode; + private String firstName; + private String secondName; + + public IncompPerson() + { + } + + public IncompPerson(String firstName, String secondName, String userCode) + { + this.firstName = firstName; + this.secondName = secondName; + this.userCode = userCode; + } + + public String getUserCode() + { + return userCode; + } + + public void setUserCode(String userCode) + { + this.userCode = userCode; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/incompatible/data/IncompPerson2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/incompatible/data/IncompPerson2.java new file mode 100644 index 000000000..a48334c0e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/incompatible/data/IncompPerson2.java @@ -0,0 +1,64 @@ +package test.eclipse.store.legacy.legacy.incompatible.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class IncompPerson2 +{ + + private String firstName; + private Integer secondName; + private String userName; + + public IncompPerson2() + { + } + + public IncompPerson2(String firstName, Integer secondName, String userName) + { + this.firstName = firstName; + this.secondName = secondName; + this.userName = userName; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public Integer getSecondName() + { + return secondName; + } + + public void setSecondName(Integer secondName) + { + this.secondName = secondName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/primitive/data/PrimitiveLegacy.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/primitive/data/PrimitiveLegacy.java new file mode 100644 index 000000000..edf61c466 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/primitive/data/PrimitiveLegacy.java @@ -0,0 +1,152 @@ +package test.eclipse.store.legacy.legacy.primitive.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class PrimitiveLegacy +{ + + private static final byte SAMPLE_BYTE = 100; + private static final short SAMPLE_SHORT = 50; + private static final int SAMPLE_INT = 5401; + private static final long SAMPLE_LONG = 1545455464654L; + private static final float SAMPLE_FLOAT = 3.141526f; + private static final double SAMPLE_DOUBLE = 3.141526545; + private static final boolean SAMPLE_BOOLEAN = Boolean.TRUE; + private static final char SAMPLE_CHAR = 'c'; + + + private byte byteValue; + private short shortValue; + private int intValue; + private long longValue; + private float floatValue; + private double doubleValue; + private boolean booleanValue; + private char charValue; + + public PrimitiveLegacy() + { + super(); + } + + public static PrimitiveLegacy fillSample() + { + PrimitiveLegacy p = new PrimitiveLegacy(); + p.fillSampleData(); + return p; + } + + public void fillSampleData() + { + this.byteValue = SAMPLE_BYTE; + this.shortValue = SAMPLE_SHORT; + this.intValue = SAMPLE_INT; + this.longValue = SAMPLE_LONG; + this.floatValue = SAMPLE_FLOAT; + this.doubleValue = SAMPLE_DOUBLE; + this.booleanValue = SAMPLE_BOOLEAN; + this.charValue = SAMPLE_CHAR; + } + + public byte getByteValue() + { + return byteValue; + } + + public short getShortValue() + { + return shortValue; + } + + public int getIntValue() + { + return intValue; + } + + public long getLongValue() + { + return longValue; + } + + public float getFloatValue() + { + return floatValue; + } + + public double getDoubleValue() + { + return doubleValue; + } + + public boolean isBooleanValue() + { + return booleanValue; + } + + public char getCharValue() + { + return charValue; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (booleanValue ? 1231 : 1237); + result = prime * result + byteValue; + result = prime * result + charValue; + long temp; + temp = Double.doubleToLongBits(doubleValue); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Float.floatToIntBits(floatValue); + result = prime * result + intValue; + result = prime * result + (int) (longValue ^ (longValue >>> 32)); + result = prime * result + shortValue; + return result; + } + + @Override + public String toString() + { + return "{" + + "byteValue=" + byteValue + + ", shortValue=" + shortValue + + ", intValue=" + intValue + + ", longValue=" + longValue + + ", floatValue=" + floatValue + + ", doubleValue=" + doubleValue + + ", booleanValue=" + booleanValue + + ", charValue=" + charValue + + '}'; + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PrimitiveLegacy that = (PrimitiveLegacy) o; + return byteValue == that.byteValue && + shortValue == that.shortValue && + intValue == that.intValue && + longValue == that.longValue && + Float.compare(that.floatValue, floatValue) == 0 && + Double.compare(that.doubleValue, doubleValue) == 0 && + booleanValue == that.booleanValue && + charValue == that.charValue; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/primitive/data/PrimitiveLegacy2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/primitive/data/PrimitiveLegacy2.java new file mode 100644 index 000000000..aefb93455 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/primitive/data/PrimitiveLegacy2.java @@ -0,0 +1,157 @@ +package test.eclipse.store.legacy.legacy.primitive.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class PrimitiveLegacy2 +{ + + private static final byte SAMPLE_BYTE = 100; + private static final short SAMPLE_SHORT = 50; + private static final int SAMPLE_INT = 5401; + private static final long SAMPLE_LONG = 1545455464654L; + private static final float SAMPLE_FLOAT = 3.141526f; + private static final double SAMPLE_DOUBLE = 3.141526545; + private static final boolean SAMPLE_BOOLEAN = Boolean.TRUE; + private static final char SAMPLE_CHAR = 'c'; + + + private Byte byteValue; + private Short shortValue; + private Integer intValue; + private Long longValue; + private Float floatValue; + private Double doubleValue; + private Boolean booleanValue; + private Character charValue; + + public PrimitiveLegacy2() + { + super(); + } + + public static PrimitiveLegacy2 fillSample() + { + PrimitiveLegacy2 p = new PrimitiveLegacy2(); + p.fillSampleData(); + return p; + } + + public void fillSampleData() + { + this.byteValue = SAMPLE_BYTE; + this.shortValue = SAMPLE_SHORT; + this.intValue = SAMPLE_INT; + this.longValue = SAMPLE_LONG; + this.floatValue = SAMPLE_FLOAT; + this.doubleValue = SAMPLE_DOUBLE; + this.booleanValue = SAMPLE_BOOLEAN; + this.charValue = SAMPLE_CHAR; + } + + public Byte getByteValue() + { + return byteValue; + } + + public void setByteValue(Byte byteValue) + { + this.byteValue = byteValue; + } + + public Short getShortValue() + { + return shortValue; + } + + public void setShortValue(Short shortValue) + { + this.shortValue = shortValue; + } + + public Integer getIntValue() + { + return intValue; + } + + public void setIntValue(Integer intValue) + { + this.intValue = intValue; + } + + public Long getLongValue() + { + return longValue; + } + + public void setLongValue(Long longValue) + { + this.longValue = longValue; + } + + public Float getFloatValue() + { + return floatValue; + } + + public void setFloatValue(Float floatValue) + { + this.floatValue = floatValue; + } + + public Double getDoubleValue() + { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) + { + this.doubleValue = doubleValue; + } + + public Boolean getBooleanValue() + { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) + { + this.booleanValue = booleanValue; + } + + public Character getCharValue() + { + return charValue; + } + + public void setCharValue(Character charValue) + { + this.charValue = charValue; + } + + @Override + public String toString() + { + return "{" + + "byteValue=" + byteValue + + ", shortValue=" + shortValue + + ", intValue=" + intValue + + ", longValue=" + longValue + + ", floatValue=" + floatValue + + ", doubleValue=" + doubleValue + + ", booleanValue=" + booleanValue + + ", charValue=" + charValue + + '}'; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferenceAddress.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferenceAddress.java new file mode 100644 index 000000000..1ffe292e1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferenceAddress.java @@ -0,0 +1,36 @@ +package test.eclipse.store.legacy.legacy.reference.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ReferenceAddress +{ + + private String street; + + public ReferenceAddress(String street) + { + this.street = street; + } + + public String getStreet() + { + return street; + } + + public void setStreet(String street) + { + this.street = street; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferencePerson.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferencePerson.java new file mode 100644 index 000000000..88141406a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferencePerson.java @@ -0,0 +1,76 @@ +package test.eclipse.store.legacy.legacy.reference.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ReferencePerson +{ + + private String userCode; + private String firstName; + private String secondName; + private ReferenceAddress adrress; + + public ReferencePerson() + { + } + + public ReferencePerson(String firstName, String secondName, String userCode, ReferenceAddress address) + { + this.firstName = firstName; + this.secondName = secondName; + this.userCode = userCode; + this.adrress = address; + } + + public String getUserCode() + { + return userCode; + } + + public void setUserCode(String userCode) + { + this.userCode = userCode; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } + + public ReferenceAddress getAdrress() + { + return adrress; + } + + public void setAdrress(ReferenceAddress adrress) + { + this.adrress = adrress; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferencePerson2.java b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferencePerson2.java new file mode 100644 index 000000000..544327b6f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/legacy/reference/data/ReferencePerson2.java @@ -0,0 +1,76 @@ +package test.eclipse.store.legacy.legacy.reference.data; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +public class ReferencePerson2 +{ + + private String firstName; + private String secondName; + private String userName; + private ReferenceAddress address2; + + public ReferencePerson2() + { + } + + public ReferencePerson2(String firstName, String secondName, String userName, ReferenceAddress address) + { + this.firstName = firstName; + this.secondName = secondName; + this.userName = userName; + this.address2 = address; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getSecondName() + { + return secondName; + } + + public void setSecondName(String secondName) + { + this.secondName = secondName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public ReferenceAddress getAddress2() + { + return address2; + } + + public void setAddress2(ReferenceAddress address2) + { + this.address2 = address2; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/primitive/PrimitiveLegacyTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/primitive/PrimitiveLegacyTest.java new file mode 100644 index 000000000..1501c047d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/primitive/PrimitiveLegacyTest.java @@ -0,0 +1,73 @@ +package test.eclipse.store.legacy.primitive; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.primitive.data.PrimitiveLegacy; +import test.eclipse.store.legacy.legacy.primitive.data.PrimitiveLegacy2; + +class PrimitiveLegacyTest +{ + + @TempDir + Path location; + + @Test + void primitiveLegacyTest() + { + + PrimitiveLegacy primitiveLegacy = PrimitiveLegacy.fillSample(); + + EmbeddedStorageManager storage = EmbeddedStorage.start(primitiveLegacy, location); + storage.shutdown(); + + PrimitiveLegacy2 primitiveLegacy2 = new PrimitiveLegacy2(); + storage = EmbeddedStorage + .Foundation(location) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.primitive.data.PrimitiveLegacy", "test.eclipse.store.legacy.legacy.primitive.data.PrimitiveLegacy2")))) + .setRoot(primitiveLegacy2) + .start(); + + assertEquals(primitiveLegacy.toString(), primitiveLegacy2.toString()); + storage.shutdown(); + primitiveLegacy = new PrimitiveLegacy(); + + storage = EmbeddedStorage + .Foundation(location) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.primitive.data.PrimitiveLegacy2", "test.eclipse.store.legacy.legacy.primitive.data.PrimitiveLegacy")))) + .setRoot(primitiveLegacy) + .start(); + + assertEquals(primitiveLegacy.toString(), primitiveLegacy2.toString()); + storage.shutdown(); + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/legacy/reference/LegacyReferenceTest.java b/integration-tests/src/test/java/test/eclipse/store/legacy/reference/LegacyReferenceTest.java new file mode 100644 index 000000000..75bdc7be1 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/legacy/reference/LegacyReferenceTest.java @@ -0,0 +1,74 @@ +package test.eclipse.store.legacy.reference; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.eclipse.serializer.collections.EqHashTable; +import org.eclipse.serializer.persistence.types.PersistenceRefactoringMappingProvider; +import org.eclipse.serializer.typing.KeyValue; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.store.legacy.legacy.reference.data.ReferenceAddress; +import test.eclipse.store.legacy.legacy.reference.data.ReferencePerson; +import test.eclipse.store.legacy.legacy.reference.data.ReferencePerson2; + +class LegacyReferenceTest +{ + + @TempDir + Path tempDir; + + private EmbeddedStorageManager storage; + + @AfterEach + void cleanStorage() + { + if (null != storage && !storage.isShutdown()) { + storage.shutdown(); + } + } + + @Test + void legacyReferenceTest() + { + ReferenceAddress address = new ReferenceAddress("Fleischgasse 18"); + ReferencePerson person = new ReferencePerson("Karel", "May", "1001", address); + + + storage = EmbeddedStorage.start(person, tempDir); + storage.shutdown(); + + ReferencePerson2 person2 = new ReferencePerson2(); + storage = EmbeddedStorage + .Foundation(tempDir) + .setRefactoringMappingProvider(PersistenceRefactoringMappingProvider.New( + EqHashTable.New( + KeyValue.New("test.eclipse.store.legacy.legacy.reference.data.ReferencePerson", "test.eclipse.store.legacy.legacy.reference.data.ReferencePerson2")))) + .setRoot(person2) + .start(); + + assertEquals(person.getUserCode(), person2.getUserName()); + assertEquals(person.getAdrress().getStreet(), person2.getAddress2().getStreet()); + storage.shutdown(); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/monitoring/MonitoringBaseTest.java b/integration-tests/src/test/java/test/eclipse/store/monitoring/MonitoringBaseTest.java new file mode 100644 index 000000000..e08a86b59 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/monitoring/MonitoringBaseTest.java @@ -0,0 +1,87 @@ +package test.eclipse.store.monitoring; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import javax.management.JMX; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import java.lang.management.ManagementFactory; +import java.nio.file.Path; +import java.util.Set; + +import org.eclipse.serializer.monitoring.MonitoringManager; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.monitoring.ObjectRegistryMonitorMBean; +import org.eclipse.store.storage.types.StorageConfiguration; +import org.eclipse.store.storage.types.StorageLiveFileProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.TypeRegister; + +public class MonitoringBaseTest +{ + + @TempDir + Path location; + + /** + * This test prove, that some of the monitoring values are still there. + * + * @throws Exception + */ + @Test + void basicTest() throws Exception + { + TypeRegister root = new TypeRegister(); + root.fillSampleDate(); + final NioFileSystem fileSystem = NioFileSystem.New(); + + String managerName = getNanoTime() + "StorageXXX"; + + StorageConfiguration configuration = StorageConfiguration.Builder() + .setStorageFileProvider(StorageLiveFileProvider.New(fileSystem.ensureDirectoryPath(location.toString()))) + .createConfiguration(); + + EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(configuration) + .setStorageMonitorManager(MonitoringManager.PlatformDependent(managerName)); + + + try (EmbeddedStorageManager storageManager = foundation.start(root)) { + + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + + ObjectName name = new ObjectName("org.eclipse.store:*,name=ObjectRegistry"); + Set names = server.queryNames(name, null); + Assertions.assertNotEquals(0, names.size()); + + ObjectRegistryMonitorMBean proxy = JMX.newMBeanProxy(server, new ObjectName("org.eclipse.store:storage=" + managerName + ",name=ObjectRegistry"), ObjectRegistryMonitorMBean.class); + Assertions.assertNotEquals(0, proxy.getSize()); + + Object invokeResult = server.getAttribute(new ObjectName("org.eclipse.store:storage=" + managerName + ",name=ObjectRegistry"), "Size"); + Assertions.assertNotEquals(0, invokeResult); + } + + } + + synchronized long getNanoTime() + { + return System.nanoTime(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/persister/InjectPersisterTest.java b/integration-tests/src/test/java/test/eclipse/store/persister/InjectPersisterTest.java new file mode 100644 index 000000000..a6ed15209 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/persister/InjectPersisterTest.java @@ -0,0 +1,51 @@ +package test.eclipse.store.persister; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * https://docs.microstream.one/manual/storage/customizing/optional-storage-manager-reference-in-entities.html + */ +public class InjectPersisterTest +{ + + @TempDir + Path location; + + + @Test + public void injectPersisterTest() + { + MyEntity root = new MyEntity("ahoj", 10); + EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, location); + storageManager.storeRoot(); + + storageManager.shutdown(); + + storageManager = EmbeddedStorage.start(root, location); + + Assertions.assertNotNull(root.getStorage()); + + storageManager.shutdown(); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/persister/MyEntity.java b/integration-tests/src/test/java/test/eclipse/store/persister/MyEntity.java new file mode 100644 index 000000000..365507801 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/persister/MyEntity.java @@ -0,0 +1,42 @@ +package test.eclipse.store.persister; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + + +import org.eclipse.serializer.persistence.types.Persister; + +public class MyEntity +{ + + String name; + int value; + transient Persister storage; + + public MyEntity(String name, int value) + { + this.name = name; + this.value = value; + } + + public Persister getStorage() + { + return storage; + } + + public void setStorage(Persister storage) + { + this.storage = storage; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/reloader/ReloaderSmokeTest.java b/integration-tests/src/test/java/test/eclipse/store/reloader/ReloaderSmokeTest.java new file mode 100644 index 000000000..600896c4f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/reloader/ReloaderSmokeTest.java @@ -0,0 +1,89 @@ +package test.eclipse.store.reloader; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; + +import org.eclipse.serializer.persistence.util.Reloader; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import test.eclipse.serializer.fixtures.TypeEnum; +import test.eclipse.serializer.fixtures.types.BinaryHandlerTestData; + +public class ReloaderSmokeTest +{ + + @TempDir + Path location; + + public static final String constString = "Hello World"; + + @Test + void reloaderBasicTest() + { + ArrayList emptyArrayList = new ArrayList<>(); + try (EmbeddedStorageManager storage = EmbeddedStorage.start(emptyArrayList, location)) { + storage.storeRoot(); + Reloader reloader = Reloader.New(storage.persistenceManager()); + + emptyArrayList.add(50); + //storage.store(emptyArrayList); + reloader.reloadDeep(emptyArrayList); + + Assertions.assertTrue(emptyArrayList.isEmpty()); + } + } + + @Test + void reloaderListString() + { + ArrayList emptyArrayList = new ArrayList<>(); + try (EmbeddedStorageManager storage = EmbeddedStorage.start(emptyArrayList, location)) { + Reloader reloader = Reloader.New(storage.persistenceManager()); + + emptyArrayList.add(constString); + //storage.store(emptyArrayList); + reloader.reloadDeep(emptyArrayList); + + Assertions.assertTrue(emptyArrayList.isEmpty()); + } + } + + @ParameterizedTest + @EnumSource(value = TypeEnum.class, names = {"Lazy"}, mode = EnumSource.Mode.EXCLUDE) + public void reloadTypeTest(TypeEnum type) + { + BinaryHandlerTestData emptyInstance = (BinaryHandlerTestData) type.createEmptyInstance(); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(emptyInstance, location)) { + emptyInstance.fillSampleData(); + + Reloader reloader = Reloader.New(storage.persistenceManager()); + + reloader.reloadDeep(emptyInstance); + + emptyInstance.proveResults(type.createEmptyInstance()); + } + + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/skill_verify/cache/SkillVerifyCacheJCacheTest.java b/integration-tests/src/test/java/test/eclipse/store/skill_verify/cache/SkillVerifyCacheJCacheTest.java new file mode 100644 index 000000000..e4322c1ca --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/skill_verify/cache/SkillVerifyCacheJCacheTest.java @@ -0,0 +1,306 @@ +package test.eclipse.store.skill_verify.cache; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; +import javax.cache.spi.CachingProvider; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.eclipse.store.cache.types.*; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies the API claims in PR #15 (cache-jcache skill rework). + * Each test maps to a specific claim in the SKILL.md / api-catalogue.md. + */ +class SkillVerifyCacheJCacheTest +{ + + /** + * SKILL.md / api-catalogue: + * The provider FQN is "org.eclipse.store.cache.types.CachingProvider" + * (no PROVIDER_CLASS_NAME constant — magic string). + */ + @Test + void provider_fqn_resolves() + { + final CachingProvider provider = Caching.getCachingProvider( + "org.eclipse.store.cache.types.CachingProvider"); + assertNotNull(provider); + assertEquals("org.eclipse.store.cache.types.CachingProvider", + provider.getClass().getName()); + } + + /** + * api-catalogue: PathProperty() = "eclipsestore.cache.configuration.path", + * DefaultResourceName() = "eclipsestore-cache.properties". + */ + @Test + void path_property_and_default_resource_name() + { + assertEquals("eclipsestore.cache.configuration.path", + CacheConfiguration.PathProperty()); + assertEquals("eclipsestore-cache.properties", + CacheConfiguration.DefaultResourceName()); + } + + /** + * api-catalogue: CacheConfigurationPropertyNames constants. + */ + @Test + void property_name_constants() + { + assertEquals("key-type", CacheConfigurationPropertyNames.KEY_TYPE); + assertEquals("value-type", CacheConfigurationPropertyNames.VALUE_TYPE); + assertEquals("storage-configuration-resource-name", CacheConfigurationPropertyNames.STORAGE_CONFIGURATION_RESOURCE_NAME); + assertEquals("read-through", CacheConfigurationPropertyNames.READ_THROUGH); + assertEquals("write-through", CacheConfigurationPropertyNames.WRITE_THROUGH); + assertEquals("store-by-value", CacheConfigurationPropertyNames.STORE_BY_VALUE); + assertEquals("statistics-enabled", CacheConfigurationPropertyNames.STATISTICS_ENABLED); + assertEquals("management-enabled", CacheConfigurationPropertyNames.MANAGEMENT_ENABLED); + assertEquals("expiry-policy-factory", CacheConfigurationPropertyNames.EXPIRY_POLICY_FACTORY); + assertEquals("eviction-manager-factory", CacheConfigurationPropertyNames.EVICTION_MANAGER_FACTORY); + assertEquals("cache-loader-factory", CacheConfigurationPropertyNames.CACHE_LOADER_FACTORY); + assertEquals("cache-writer-factory", CacheConfigurationPropertyNames.CACHE_WRITER_FACTORY); + } + + /** + * SKILL.md Pattern F + api-catalogue: EvictionManager static factories. + */ + @Test + void evictionManager_factories_exist() + { + final EvictionPolicy lru = EvictionPolicy.LeastRecentlyUsed(1000L); + assertNotNull(lru); + assertNotNull(EvictionManager.OnEntryCreation(lru)); + assertNotNull(EvictionManager.Interval(lru, 60_000L)); + } + + /** + * SKILL.md Pattern F + api-catalogue: EvictionPolicy static factories. + */ + @Test + void evictionPolicy_factories_exist() + { + assertNotNull(EvictionPolicy.LeastRecentlyUsed(1000L)); + assertNotNull(EvictionPolicy.LeastFrequentlyUsed(1000L)); + assertNotNull(EvictionPolicy.FirstInFirstOut(4, 1000L)); + assertNotNull(EvictionPolicy.BiggestObjects(4, 1000L)); + } + + /** + * api-catalogue: org.eclipse.store.cache.types.Cache extends javax.cache.Cache and Unwrappable; + * adds size(), putSilent(K,V), narrows getCacheManager() / getConfiguration(). + */ + @Test + void eclipseStore_cache_interface_shape() throws NoSuchMethodException + { + final Class esCache = org.eclipse.store.cache.types.Cache.class; + assertTrue(javax.cache.Cache.class.isAssignableFrom(esCache), + "Eclipse Store Cache must extend javax.cache.Cache"); + assertTrue(Unwrappable.class.isAssignableFrom(esCache), + "Eclipse Store Cache must extend Unwrappable"); + + // Eclipse-Store-only methods declared on the narrowing interface + final Method size = esCache.getMethod("size"); + assertEquals(long.class, size.getReturnType()); + + final Method putSilent = esCache.getMethod("putSilent", Object.class, Object.class); + assertNotNull(putSilent); + } + + /** + * api-catalogue: org.eclipse.store.cache.types.CacheManager extends + * javax.cache.CacheManager and adds removeCache(String). + */ + @Test + void eclipseStore_cacheManager_interface_shape() throws NoSuchMethodException + { + final Class esManager = org.eclipse.store.cache.types.CacheManager.class; + assertTrue(javax.cache.CacheManager.class.isAssignableFrom(esManager), + "Eclipse Store CacheManager must extend javax.cache.CacheManager"); + + final Method removeCache = esManager.getMethod("removeCache", String.class); + assertNotNull(removeCache); + } + + /** + * SKILL.md Mental model: + * "A storage-backed cache automatically acts as both CacheReader and CacheWriter + * — readThrough and writeThrough default to true." + */ + @Test + void storage_backed_cache_defaults_readThrough_writeThrough_to_true(@TempDir Path dir) + { + try (EmbeddedStorageManager storage = EmbeddedStorage.start(dir)) { + final CacheConfiguration cfg = CacheConfiguration + .Builder(String.class, String.class, "rwt", storage) + .build(); + assertTrue(cfg.isReadThrough(), + "Storage-backed cache must default readThrough=true"); + assertTrue(cfg.isWriteThrough(), + "Storage-backed cache must default writeThrough=true"); + } + } + + /** + * SKILL.md Pattern B + Anti-pattern 1: + * Standalone configuration loses entries on restart; + * storage-backed configuration survives. + */ + @Test + void storage_backed_cache_round_trips_across_restart(@TempDir Path dir) + { + // First boot: put entry, shut down. + try (EmbeddedStorageManager storage = EmbeddedStorage.start(dir)) { + final CachingProvider provider = Caching.getCachingProvider( + "org.eclipse.store.cache.types.CachingProvider"); + try (CacheManager cm = provider.getCacheManager()) { + final CacheConfiguration cfg = CacheConfiguration + .Builder(String.class, String.class, "persisted-cache", storage) + .expiryPolicyFactory(CreatedExpiryPolicy.factoryOf( + new Duration(TimeUnit.HOURS, 1))) + .build(); + final Cache cache = cm.createCache("persisted-cache", cfg); + cache.put("k1", "v1"); + assertEquals("v1", cache.get("k1")); + } + } + + // Second boot: open storage + cache again, expect entry present. + try (EmbeddedStorageManager storage = EmbeddedStorage.start(dir)) { + final CachingProvider provider = Caching.getCachingProvider( + "org.eclipse.store.cache.types.CachingProvider"); + try (CacheManager cm = provider.getCacheManager()) { + final CacheConfiguration cfg = CacheConfiguration + .Builder(String.class, String.class, "persisted-cache", storage) + .expiryPolicyFactory(CreatedExpiryPolicy.factoryOf( + new Duration(TimeUnit.HOURS, 1))) + .build(); + final Cache cache = cm.createCache("persisted-cache", cfg); + assertEquals("v1", cache.get("k1"), + "Storage-backed cache entry must survive restart"); + } + } + } + + /** + * SKILL.md "Key advantage over Ehcache / Caffeine": + * "Cache values are not required to be Serializable when storeByValue(false) is used." + */ + @Test + void non_serializable_value_works_with_storeByValue_false() + { + final CachingProvider provider = Caching.getCachingProvider( + "org.eclipse.store.cache.types.CachingProvider"); + try (CacheManager cm = provider.getCacheManager()) { + final MutableConfiguration cfg = + new MutableConfiguration() + .setTypes(String.class, NonSerializable.class) + .setStoreByValue(false); + + final Cache cache = + cm.createCache("non-ser-cache", cfg); + + final NonSerializable value = new NonSerializable("hello"); + assertDoesNotThrow(() -> cache.put("k", value)); + assertEquals("hello", cache.get("k").label); + + cm.destroyCache("non-ser-cache"); + } + } + + /** + * A class that does NOT implement Serializable — to verify storeByReference cache path. + */ + public static class NonSerializable + { + public final String label; + + public NonSerializable(String label) + { + this.label = label; + } + // not Serializable — verifies the skill claim + } + + /** + * SKILL.md Pattern G: + * javax.cache.Caching.getCachingProvider() works only with one provider on classpath. + * With Eclipse Store as the sole provider here, the no-arg variant must return it. + */ + @Test + void no_arg_getCachingProvider_works_with_single_provider() + { + final CachingProvider provider = Caching.getCachingProvider(); + assertNotNull(provider); + // In this test environment Eclipse Store is the only provider on classpath + assertEquals("org.eclipse.store.cache.types.CachingProvider", + provider.getClass().getName(), + "With Eclipse Store as the only provider, the no-arg lookup must resolve to it"); + } + + /** + * Sanity: javax.cache.spi.CachingProvider FQN, asserted by the skill, is real. + */ + @Test + void javax_cache_provider_fqn() + { + // documented to ensure the class name string in the skill matches + assertEquals("javax.cache.spi.CachingProvider", + CachingProvider.class.getName()); + } + + /** + * verify that Cache values in a storage-backed config with non-Serializable + * types still don't require Serializable. + */ + @Test + void storage_backed_cache_does_not_require_Serializable(@TempDir Path dir) + { + try (EmbeddedStorageManager storage = EmbeddedStorage.start(dir)) { + final CachingProvider provider = Caching.getCachingProvider( + "org.eclipse.store.cache.types.CachingProvider"); + try (CacheManager cm = provider.getCacheManager()) { + final CacheConfiguration cfg = CacheConfiguration + .Builder(String.class, NonSerializable.class, "ns-storage", storage) + .storeByReference() + .build(); + final Cache cache = + cm.createCache("ns-storage", cfg); + assertDoesNotThrow(() -> cache.put("k", new NonSerializable("ok"))); + assertEquals("ok", cache.get("k").label); + + // Sanity: NonSerializable is really not Serializable + assertTrue(!Serializable.class.isAssignableFrom(NonSerializable.class)); + + cm.destroyCache("ns-storage"); + } + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/BigListTest.java b/integration-tests/src/test/java/test/eclipse/store/various/BigListTest.java new file mode 100644 index 000000000..31d024ce7 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/BigListTest.java @@ -0,0 +1,64 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BigListTest +{ + + @TempDir + Path tempDir; + + + @Test + void bigListSaveTest() + { + int n = 1_000_000; + + List list = IntStream.range(0, n) + .parallel() + .mapToObj(i -> { + StringBuilder sb = new StringBuilder(); + int length = ThreadLocalRandom.current().nextInt(10) + 1; + for (int j = 0; j < length; j++) { + char c = (char) (ThreadLocalRandom.current().nextInt(26) + 'a'); + sb.append(c); + } + return sb.toString(); + }) + .collect(Collectors.toList()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, tempDir)) { + + } + List loaded = new ArrayList<>(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + + } + Assertions.assertIterableEquals(list, loaded); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/ClazzPrimitiveTest.java b/integration-tests/src/test/java/test/eclipse/store/various/ClazzPrimitiveTest.java new file mode 100644 index 000000000..387f73ba8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/ClazzPrimitiveTest.java @@ -0,0 +1,153 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.Serializable; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class ClazzPrimitiveTest +{ + // --- helper types used as Class representatives --- + + interface SampleInterface + { + } + + enum SampleEnum + {VALUE} + + @SuppressWarnings("InnerClassMayBeStatic") + abstract static class SampleAbstractClass + { + } + + @interface SampleAnnotation + { + } + + // --- test data --- + + static Stream> allClassVariants() + { + return Stream.of( + // Primitive types + int.class, + long.class, + double.class, + float.class, + boolean.class, + byte.class, + short.class, + char.class, + // Void pseudo-type + //void.class, //https://github.com/eclipse-serializer/serializer/issues/247 + // Boxed types + Integer.class, + Long.class, + Double.class, + Float.class, + Boolean.class, + Byte.class, + Short.class, + Character.class, + Void.class, + // Common reference types + String.class, + Object.class, + Number.class, + // Interface + SampleInterface.class, + Serializable.class, + Iterable.class, + // Enum + SampleEnum.class, + // Abstract class + SampleAbstractClass.class, + // Annotation type + SampleAnnotation.class, + // Array types – primitives + int[].class, + long[].class, + double[].class, + float[].class, + boolean[].class, + byte[].class, + short[].class, + char[].class, + // Array types – reference + String[].class, + Object[].class, + Integer[].class, + // Multi-dimensional arrays + int[][].class, + String[][].class + ); + } + + @TempDir + Path tempDir; + + @ParameterizedTest(name = "clazzTest [{index}] {0}") + @MethodSource("allClassVariants") + void clazzTest(Class clazz) + { + ClazzData root = new ClazzData(clazz); + try (EmbeddedStorageManager manager = EmbeddedStorage.start(root, tempDir)) { + manager.storeRoot(); + } + + ClazzData root1 = new ClazzData(); + try (EmbeddedStorageManager manager = EmbeddedStorage.start(root1, tempDir)) { + System.out.println("Loaded class: " + root1.getClazz()); + assertEquals(clazz.getTypeName(), root1.getClazz().getTypeName(), + "Type name mismatch for: " + clazz); + } + } + + // --- root object --- + + private static class ClazzData + { + private Class clazz; + + public ClazzData(Class clazz) + { + this.clazz = clazz; + } + + public ClazzData() + { + } + + public Class getClazz() + { + return clazz; + } + + public void setClazz(Class clazz) + { + this.clazz = clazz; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/ClazzVoidTest.java b/integration-tests/src/test/java/test/eclipse/store/various/ClazzVoidTest.java new file mode 100644 index 000000000..97b7e3e51 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/ClazzVoidTest.java @@ -0,0 +1,69 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Disabled("https://github.com/eclipse-serializer/serializer/issues/247") +public class ClazzVoidTest +{ + + @TempDir + Path tempDir; + + @Test + void clazzVoidTest() + { + VoidClazz root = new VoidClazz(void.class); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(root, tempDir)) { + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + VoidClazz loaded = (VoidClazz) manager.root(); + assertEquals(loaded.getClazz(), void.class); + } + + } + + + private static class VoidClazz + { + Class clazz; + + public VoidClazz() + { + } + + public VoidClazz(Class clazz) + { + this.clazz = clazz; + } + + public Class getClazz() + { + return clazz; + } + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/ConcurrencyHelpersTest.java b/integration-tests/src/test/java/test/eclipse/store/various/ConcurrencyHelpersTest.java new file mode 100644 index 000000000..ababeab2e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/ConcurrencyHelpersTest.java @@ -0,0 +1,119 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.serializer.concurrency.LockScope; +import org.eclipse.serializer.concurrency.LockedExecutor; +import org.junit.jupiter.api.Test; + +public class ConcurrencyHelpersTest extends LockScope +{ + + @Test + void concurrencyHelpersTest() throws InterruptedException + { + List list = new ArrayList<>(); + int threadCount = 50; + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + Thread thread = new Thread( + () -> { + write( + () -> { + for (int j = 0; j < 1000; j++) { + list.add(System.currentTimeMillis()); + } + } + ); + read( + () -> { + List collect = list.parallelStream() + .map(l -> l.toString()) + .collect(Collectors.toList()); + } + ); + write( + () -> { + for (int j = 0; j < 1000; j++) { + list.add(System.currentTimeMillis()); + } + } + ); + } + ); + + threads[i] = thread; + thread.start(); + + } + + + for (int i = 0; i < threadCount; i++) { + threads[i].join(); + } + } + + + @Test + void concurrencyHelpers_direct_Test() throws InterruptedException + { + final LockedExecutor executor = LockedExecutor.New(); + + List list = new ArrayList<>(); + int threadCount = 50; + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + Thread thread = new Thread( + () -> { + executor.write( + () -> { + for (int j = 0; j < 1000; j++) { + list.add(System.currentTimeMillis()); + } + } + ); + executor.read( + () -> { + List collect = list.parallelStream() + .map(l -> l.toString()) + .collect(Collectors.toList()); + } + ); + executor.write( + () -> { + for (int j = 0; j < 1000; j++) { + list.add(System.currentTimeMillis()); + } + } + ); + } + ); + + threads[i] = thread; + thread.start(); + + } + + + for (int i = 0; i < threadCount; i++) { + threads[i].join(); + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/EagerFieldEvaluatorTest.java b/integration-tests/src/test/java/test/eclipse/store/various/EagerFieldEvaluatorTest.java new file mode 100644 index 000000000..e6f6df41f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/EagerFieldEvaluatorTest.java @@ -0,0 +1,156 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore GigaMap + * %% + * Copyright (C) 2023 - 2025 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.nio.file.Path; + +import org.eclipse.serializer.persistence.types.PersistenceEagerStoringFieldEvaluator; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies that a custom {@link PersistenceEagerStoringFieldEvaluator} registered via + * {@code onConnectionFoundation(cf -> cf.setReferenceFieldEagerEvaluator(...))} actually + * forces eager traversal through annotated fields while leaving unannotated fields lazy. + * + *

Scenario: + *

    + *
  • A root holds two {@link Holder} references — one annotated {@link StoreEager}, one not.
  • + *
  • After the initial store both holders are registered (have an objectId).
  • + *
  • On reopen, the {@code value} field of both holders is mutated in place.
  • + *
  • {@code storageManager.storeRoot()} is invoked — a lazy convenience store.
  • + *
  • By the lazy rule the holders should be skipped (already registered children), + * but the per-field eager evaluator must force the eager-marked field's holder + * to be re-traversed and re-written.
  • + *
+ * + *

Expectation after reopening the storage: + *

    + *
  • Eager-annotated holder reflects the new value.
  • + *
  • Non-annotated (lazy) holder still reflects the old value.
  • + *
+ */ +public class EagerFieldEvaluatorTest +{ + @TempDir + Path storageDir; + + @Test + void eagerAnnotatedFieldIsRewrittenOnLazyRootStore() + { + // --- 1) Initial setup: write the root with two holders carrying old values --- + try (EmbeddedStorageManager manager = newManager(storageDir)) { + final Root root = new Root(); + root.eagerHolder = new Holder("eager-OLD"); + root.lazyHolder = new Holder("lazy-OLD"); + manager.setRoot(root); + manager.storeRoot(); + } + + // --- 2) Reopen, mutate both holders in place, then storeRoot() (lazy convenience) --- + try (EmbeddedStorageManager manager = newManager(storageDir)) { + final Root root = (Root) manager.root(); + // Sanity check: persisted state from step 1. + assertEquals("eager-OLD", root.eagerHolder.value); + assertEquals("lazy-OLD", root.lazyHolder.value); + + // In-place mutation of an already-registered child object. + // A pure lazy walk from root would skip both holders. + root.eagerHolder.value = "eager-NEW"; + root.lazyHolder.value = "lazy-NEW"; + + // Lazy convenience store on the root. The per-field evaluator must + // force descent through Root#eagerHolder and re-write that Holder. + manager.storeRoot(); + } + + // --- 3) Reopen again and verify what actually hit the disk --- + try (EmbeddedStorageManager manager = newManager(storageDir)) { + final Root root = (Root) manager.root(); + + // Eager-annotated field: must reflect the new value. + assertEquals("eager-NEW", root.eagerHolder.value, + "Field annotated with @StoreEager should have been re-written by the per-field eager evaluator."); + + // Non-annotated field: lazy walk skipped the already-registered holder, + // so the in-place mutation must NOT have been persisted. + assertEquals("lazy-OLD", root.lazyHolder.value, + "Non-annotated field should follow lazy semantics: in-place mutation of a registered child must not be persisted by storeRoot()."); + } + } + + private static EmbeddedStorageManager newManager(final Path dir) + { + return EmbeddedStorage.Foundation(dir) + .onConnectionFoundation(cf -> cf.setReferenceFieldEagerEvaluator(new StoreEagerEvaluator())) + .createEmbeddedStorageManager() + .start(); + } + + // ----- Test fixtures ------------------------------------------------------------- + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface StoreEager + { + // marker + } + + /** + * Field evaluator that forces eager traversal for any field annotated with {@link StoreEager}. + */ + public static class StoreEagerEvaluator implements PersistenceEagerStoringFieldEvaluator + { + @Override + public boolean isEagerStoring(final Class clazz, final Field field) + { + return field.isAnnotationPresent(StoreEager.class); + } + } + + public static class Root + { + @StoreEager + Holder eagerHolder; + + Holder lazyHolder; + } + + public static class Holder + { + + String value; + + Holder() + { + // for deserialization + } + + Holder(final String value) + { + this.value = value; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/LazyNullTest.java b/integration-tests/src/test/java/test/eclipse/store/various/LazyNullTest.java new file mode 100644 index 000000000..46bd673dd --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/LazyNullTest.java @@ -0,0 +1,57 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.nio.file.Path; + +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LazyNullTest +{ + @TempDir + Path tempDir; + + @Test + void lazyNullTest() + { + Root root = new Root(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + assertNull(root.getLazyString().get()); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + root = (Root) storageManager.root(); + assertNull(root.getLazyString().get()); + } + + } + + private static class Root + { + private Lazy lazyString = Lazy.Reference(null); + + public Lazy getLazyString() + { + return lazyString; + } + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/LockingFeatureTest.java b/integration-tests/src/test/java/test/eclipse/store/various/LockingFeatureTest.java new file mode 100644 index 000000000..26717cf4a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/LockingFeatureTest.java @@ -0,0 +1,69 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LockingFeatureTest +{ + + @TempDir + static Path workDir; + + ArrayList data = new ArrayList<>(); + + //https://docs.eclipsestore.io/manual/storage/configuration/lock-file.html + @Test + void lockingFeature() + { + try (EmbeddedStorageManager storageManager = EmbeddedStorage.Foundation(workDir) + .setLockFileSetupProvider(Storage.LockFileSetupProvider()) + .start(data); + ) { + data.add("some data"); + storageManager.store(data); + + Boolean exists = Files.exists(Paths.get(workDir.toString(), "used.lock")); + assertTrue(exists); + + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.Foundation(workDir) + .setLockFileSetupProvider(Storage.LockFileSetupProvider()) + .start(data); + ) { + data.add("some more data"); + storageManager.store(data); + Boolean exists = Files.exists(Paths.get(workDir.toString(), "used.lock")); + assertTrue(exists); + } + + //Question: Should the file stands there? +// Boolean b = Files.exists(Paths.get(workDir.toString(), "used.lock")); +// assertFalse(b); + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/ReadOnlyConcurrentIssue25ExperimentTest.java b/integration-tests/src/test/java/test/eclipse/store/various/ReadOnlyConcurrentIssue25ExperimentTest.java new file mode 100644 index 000000000..cf98203ee --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/ReadOnlyConcurrentIssue25ExperimentTest.java @@ -0,0 +1,106 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.exceptions.StorageExceptionInitialization; +import org.eclipse.store.storage.types.StorageWriteControllerReadOnlyMode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Experimental verification of microstream-one/internal#25: + * a second, read-only EmbeddedStorageManager on the same storage directory + * within the same JVM while the first (writing) manager is still running. + */ +public class ReadOnlyConcurrentIssue25ExperimentTest +{ + @TempDir + Path workDir; + + /** + * The exact scenario from the issue: second foundation on the same directory + * with the default database name. Expected to still fail with + * "Active storage ... already exists" (Database.guaranteeNoActiveStorage). + */ + @Test + void originalRepro_secondManagerWhileFirstRunning_stillThrows() + { + List root = new ArrayList<>(List.of("a", "b", "c")); + + try (EmbeddedStorageManager writer = EmbeddedStorage.start(root, workDir)) { + EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(workDir); + StorageWriteControllerReadOnlyMode writeController = + new StorageWriteControllerReadOnlyMode(foundation.getWriteController()); + writeController.setReadOnly(true); + foundation.setWriteController(writeController); + + StorageExceptionInitialization e = Assertions.assertThrows( + StorageExceptionInitialization.class, + () -> foundation.setRoot(new ArrayList()).createEmbeddedStorageManager() + ); + Assertions.assertTrue(e.getMessage().contains("already exists"), e.getMessage()); + } + } + + /** + * Same scenario, but the second foundation gets its own database name, + * which bypasses the Databases-registry guard. The read-only manager + * should start next to the running writer, read the data and reject writes. + */ + @Test + void setDataBaseName_bypassesGuard_readOnlyManagerReadsLiveStorage() + { + List root = new ArrayList<>(List.of("a", "b", "c")); + + try (EmbeddedStorageManager writer = EmbeddedStorage.start(root, workDir)) { + EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(workDir); + StorageWriteControllerReadOnlyMode writeController = + new StorageWriteControllerReadOnlyMode(foundation.getWriteController()); + writeController.setReadOnly(true); + foundation.setWriteController(writeController); + foundation.setDataBaseName("read-only-experiment"); + + try (EmbeddedStorageManager readOnly = + foundation.setRoot(new ArrayList()).createEmbeddedStorageManager().start()) { + Assertions.assertTrue(writer.isRunning()); + Assertions.assertTrue(readOnly.isRunning()); + + @SuppressWarnings("unchecked") + List loaded = (List) readOnly.root(); + Assertions.assertEquals(List.of("a", "b", "c"), loaded); + + RuntimeException writeRejected = Assertions.assertThrows( + RuntimeException.class, + () -> readOnly.store(loaded) + ); + System.out.println("[experiment] write on read-only manager rejected with: " + + writeRejected.getClass().getName() + ": " + writeRejected.getMessage()); + } + + // writer must stay alive and writable after the read-only manager is closed + Assertions.assertTrue(writer.isRunning()); + root.add("d"); + writer.store(root); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/ReadOnlyTest.java b/integration-tests/src/test/java/test/eclipse/store/various/ReadOnlyTest.java new file mode 100644 index 000000000..5a02fa33c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/ReadOnlyTest.java @@ -0,0 +1,198 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.CRC32; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageWriteControllerReadOnlyMode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.TypeRegister; + +public class ReadOnlyTest +{ + @TempDir + Path workDir; + + @Test + void readOnlyTest() throws IOException, NoSuchAlgorithmException, InterruptedException + { + TypeRegister register = new TypeRegister(); + register.fillSampleDate(); + + List typeRegisters = new ArrayList<>(); + + for (int i = 0; i < 5; i++) { + TypeRegister register1 = new TypeRegister(); + register1.fillSampleDate(); + typeRegisters.add(register1); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(typeRegisters, workDir)) { + typeRegisters.clear(); + storageManager.storeRoot(); + } + + Map checksums = FastChecksumCalculator.calculateAttributes(workDir); + + EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(workDir); + final StorageWriteControllerReadOnlyMode storageWriteController = + new StorageWriteControllerReadOnlyMode(foundation.getWriteController()); + storageWriteController.setReadOnly(true); + foundation.setWriteController(storageWriteController); + + List newRegister = new ArrayList<>(); + + try (EmbeddedStorageManager readOnlyStoreManager = foundation.setRoot(newRegister).createEmbeddedStorageManager()) { + } + + Map checksums2 = FastChecksumCalculator.calculateAttributes(workDir); + Assertions.assertEquals(checksums, checksums2); + + } + + @Test + void simpleReadOnly_isRunning() + { + + String data = "akfdjlsjfkdwjljd"; + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(data, workDir)) { + } + + EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(workDir); + final StorageWriteControllerReadOnlyMode storageWriteController = + new StorageWriteControllerReadOnlyMode(foundation.getWriteController()); + storageWriteController.setReadOnly(true); + foundation.setWriteController(storageWriteController); + + try (EmbeddedStorageManager readOnlyStoreManager = foundation.setRoot(data).createEmbeddedStorageManager().start()) { + Assertions.assertTrue(readOnlyStoreManager.isRunning()); + } + } + + +// java + + + private static class FastChecksumCalculator + { + + // Fastest check: only size + last modification + public static Map calculateAttributes(Path directoryPath) throws IOException + { + Objects.requireNonNull(directoryPath); + try (Stream paths = Files.walk(directoryPath)) { + return paths.filter(Files::isRegularFile) + .collect(Collectors.toMap( + Path::toString, + p -> { + try { + long size = Files.size(p); + FileTime ft = Files.getLastModifiedTime(p); + return size + "-" + ft.toMillis(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + )); + } catch (UncheckedIOException uio) { + throw uio.getCause(); + } + } + + // Fast CRC32 checksum (file reading) with parallel processing + public static Map calculateCrc32Parallel(Path directoryPath) throws IOException + { + Objects.requireNonNull(directoryPath); + Map result = new ConcurrentHashMap<>(); + try (Stream paths = Files.walk(directoryPath)) { + paths.parallel() + .filter(Files::isRegularFile) + .forEach(p -> { + try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(p))) { + CRC32 crc = new CRC32(); + byte[] buf = new byte[8 * 1024]; + int r; + while ((r = in.read(buf)) != -1) { + crc.update(buf, 0, r); + } + result.put(p.toString(), Long.toHexString(crc.getValue())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + return result; + } catch (UncheckedIOException uio) { + throw uio.getCause(); + } + } + } + + + private static class ChecksumCalculator + { + + public static Map calculateChecksums(Path directoryPath) throws IOException, NoSuchAlgorithmException + { + Map checksums = new HashMap<>(); + try (Stream paths = Files.walk(directoryPath)) { + paths.filter(Files::isRegularFile).forEach(path -> { + try { + checksums.put(path.toString(), calculateChecksum(path)); + } catch (IOException | NoSuchAlgorithmException e) { + e.printStackTrace(); + } + }); + } + return checksums; + } + + private static String calculateChecksum(Path filePath) throws IOException, NoSuchAlgorithmException + { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + try (DigestInputStream dis = new DigestInputStream(new FileInputStream(filePath.toFile()), md)) { + while (dis.read() != -1) ; //empty loop to clear the data + md = dis.getMessageDigest(); + } + + // bytes to hex + StringBuilder result = new StringBuilder(); + for (byte b : md.digest()) { + result.append(String.format("%02x", b)); + } + return result.toString(); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/ReloaderTest.java b/integration-tests/src/test/java/test/eclipse/store/various/ReloaderTest.java new file mode 100644 index 000000000..5a1baf13a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/ReloaderTest.java @@ -0,0 +1,88 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.serializer.persistence.util.Reloader; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +//https://github.com/eclipse-serializer/serializer/issues/135 +public class ReloaderTest +{ + + @TempDir + Path workDir; + + @Test + void reloaderTest() + { + ReloaderTestRoot root = new ReloaderTestRoot("Hello", "World", 42); + try (EmbeddedStorageManager storage = EmbeddedStorage.start(root, workDir)) { + root.lazy.clear(); + Reloader.New(storage.persistenceManager()).reloadDeep(storage.root()); + } + + } + + static class ReloaderTestRoot + { + private String name; + private Lazy lazy; + private transient Integer transientField; + + public ReloaderTestRoot(String name, String lazy, Integer transientField) + { + this.name = name; + this.lazy = Lazy.Reference(lazy); + this.transientField = transientField; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public Lazy getLazy() + { + return lazy; + } + + public void setLazy(Lazy lazy) + { + this.lazy = lazy; + } + + public Integer getTransientField() + { + return transientField; + } + + public void setTransientField(Integer transientField) + { + this.transientField = transientField; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/RestartStorageTest.java b/integration-tests/src/test/java/test/eclipse/store/various/RestartStorageTest.java new file mode 100644 index 000000000..42c841adc --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/RestartStorageTest.java @@ -0,0 +1,52 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageEntityCache; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.TypeRegister; + +public class RestartStorageTest +{ + + @TempDir + Path location; + + @TempDir + Path storageLocation; + private EmbeddedStorageManager manager; + + @Test + public void restartTest() throws InterruptedException + { + StorageEntityCache.Default.setGarbageCollectionEnabled(true); + TypeRegister register = new TypeRegister(); + register.fillSampleDate(); + manager = EmbeddedStorage.start(register, storageLocation); + manager.store(register); + manager.shutdown(); + + manager = EmbeddedStorage.start(register, storageLocation); + manager.store(register); + manager.shutdown(); + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/SecondStartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/SecondStartTest.java new file mode 100644 index 000000000..daf4c3ec7 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/SecondStartTest.java @@ -0,0 +1,233 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.afs.types.AFileSystem; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.eclipse.store.storage.types.StorageBackupFileProvider; +import org.eclipse.store.storage.types.StorageBackupSetup; +import org.eclipse.store.storage.types.StorageLiveFileProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import test.eclipse.serializer.fixtures.TypeRegister; + +public class SecondStartTest +{ + + @Test + public void twoStartTest(@TempDir Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + storage.shutdown(); + storage.start(); + storage.shutdown(); + } + + @Test + public void twoStart_defaultRoot_withTempDir(@TempDir Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + storage.shutdown(); + storage.start(); + storage.shutdown(); + } + + @Test + public void twoStart_pojoRoot(@TempDir Path dir) + { + final List root = new ArrayList<>(); + root.add("alpha"); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(root, dir); + storage.storeRoot(); + storage.shutdown(); + + storage.start(); + final List reloaded = storage.root(); + assertNotNull(reloaded); + assertEquals(1, reloaded.size()); + assertEquals("alpha", reloaded.get(0)); + storage.shutdown(); + } + + @Test + public void twoStart_typeRegisterRoot(@TempDir Path dir) + { + final TypeRegister register = new TypeRegister(); + register.fillSampleDate(); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(register, dir); + storage.storeRoot(); + storage.shutdown(); + + storage.start(); + final TypeRegister reloaded = storage.root(); + assertNotNull(reloaded); + storage.shutdown(); + } + + @Test + public void twoStart_setRootAfterFirstStart(@TempDir Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + assertNull(storage.root()); + + final List newRoot = new ArrayList<>(); + newRoot.add("beta"); + storage.setRoot(newRoot); + storage.storeRoot(); + storage.shutdown(); + + storage.start(); + final List reloaded = storage.root(); + assertNotNull(reloaded); + assertEquals(1, reloaded.size()); + assertEquals("beta", reloaded.get(0)); + storage.shutdown(); + } + + @Test + public void twoStart_withBackup_builder(@TempDir Path dir, @TempDir Path backup) + { + final List root = new ArrayList<>(); + root.add("alpha"); + + final EmbeddedStorageManager storage = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(dir.toAbsolutePath().toString()) + .setBackupDirectory(backup.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(root) + .start(); + storage.storeRoot(); + storage.shutdown(); + + final long backupSizeAfterFirstShutdown = totalFileSize(backup); + assertTrue(backupSizeAfterFirstShutdown > 0, + "Backup must contain data after first shutdown, got " + backupSizeAfterFirstShutdown + " bytes"); + + storage.start(); + final List reloaded = storage.root(); + assertNotNull(reloaded); + assertEquals(1, reloaded.size()); + assertEquals("alpha", reloaded.get(0)); + + reloaded.add("beta"); + storage.store(reloaded); + storage.shutdown(); + + final long backupSizeAfterSecondShutdown = totalFileSize(backup); + final long liveSizeAfterSecondShutdown = totalFileSize(dir); + + // Independently verify the backup is a faithful copy by opening it as primary storage. + final EmbeddedStorageManager verifier = EmbeddedStorage.start(new ArrayList(), backup); + final List fromBackup = verifier.root(); + assertNotNull(fromBackup); + assertEquals(2, fromBackup.size(), + "Backup must contain both entries written across two start cycles. " + + "backup bytes: " + backupSizeAfterFirstShutdown + " -> " + backupSizeAfterSecondShutdown + + ", live bytes after 2nd shutdown: " + liveSizeAfterSecondShutdown + + ", fromBackup: " + fromBackup); + assertEquals("alpha", fromBackup.get(0)); + assertEquals("beta", fromBackup.get(1)); + verifier.shutdown(); + + assertEquals(liveSizeAfterSecondShutdown, backupSizeAfterSecondShutdown, + "Backup must mirror the live storage byte-for-byte in total size after shutdown"); + assertTrue(backupSizeAfterSecondShutdown > backupSizeAfterFirstShutdown, + "Backup must keep receiving writes after restart: " + + backupSizeAfterFirstShutdown + " -> " + backupSizeAfterSecondShutdown + " bytes"); + } + + private static long totalFileSize(final Path directory) + { + try (java.util.stream.Stream stream = java.nio.file.Files.walk(directory)) { + return stream + .filter(java.nio.file.Files::isRegularFile) + .mapToLong(p -> { + try { + return java.nio.file.Files.size(p); + } catch (java.io.IOException e) { + throw new RuntimeException(e); + } + }) + .sum(); + } catch (java.io.IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void twoStart_foundationApi_withBackup(@TempDir Path dir, @TempDir Path backup) + { + final AFileSystem fs = NioFileSystem.New(); + final ADirectory dataDir = fs.ensureDirectoryPath(dir.toFile().getAbsolutePath()); + final ADirectory backupDir = fs.ensureDirectoryPath(backup.toFile().getAbsolutePath()); + + final StorageBackupSetup backupSetup = StorageBackupSetup.New( + StorageBackupFileProvider.New(backupDir) + ); + + final EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setBackupSetup(backupSetup) + .setStorageFileProvider(StorageLiveFileProvider.New(dataDir)) + .createConfiguration() + ); + + final TypeRegister register = new TypeRegister(); + register.fillSampleDate(); + + final EmbeddedStorageManager storage = foundation + .createEmbeddedStorageManager(register) + .start(); + storage.storeRoot(); + storage.shutdown(); + + storage.start(); + final TypeRegister reloaded = storage.root(); + assertNotNull(reloaded); + storage.shutdown(); + } + + @Test + public void threeStartCycles_pojoRoot(@TempDir Path dir) + { + final List root = new ArrayList<>(); + root.add("entry-0"); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(root, dir); + storage.storeRoot(); + storage.shutdown(); + + for (int i = 0; i < 3; i++) { + storage.start(); + assertNotNull(storage.root()); + storage.shutdown(); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/StorageSizeLimit.java b/integration-tests/src/test/java/test/eclipse/store/various/StorageSizeLimit.java new file mode 100644 index 000000000..1e09eb0cc --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/StorageSizeLimit.java @@ -0,0 +1,121 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; + +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +public class StorageSizeLimit +{ + + @TempDir + static Path tempDir; + + + @Test + @Disabled("Takes too long") + void sizeLimitTest() + { + + byte[] b = new byte[1024 * 1024 * 1024]; + Root root = new Root(b); + System.out.println(tempDir.toAbsolutePath().toString()); + try (EmbeddedStorageManager storageManager = prepareFoundation().start(root)) { + byte[] data1 = new byte[1024 * 1024 * 1024]; + byte[] data2 = new byte[1024 * 1024 * 1024]; + System.out.println(data1.length); + root.setData1(data1); + root.setData2(data2); + storageManager.storeRoot(); + } catch (Exception e) { + e.printStackTrace(); + } + + + Root root1 = new Root(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root1, tempDir)) { + System.out.println(root1.data.length); + + } + + System.out.println("Data stored"); + + } + + private static EmbeddedStorageFoundation prepareFoundation() + { + + return EmbeddedStorageConfigurationBuilder.New() + .setDataFileMaximumSize(ByteSize.New(2, ByteUnit.GB)) + .setStorageDirectory(tempDir.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation(); + } + + private static class Root + { + private byte[] data; + private byte[] data1; + private byte[] data2; + + public Root() + { + } + + public Root(byte[] data) + { + this.data = data; + } + + public byte[] getData() + { + return data; + } + + public void setData(byte[] data) + { + this.data = data; + } + + public void setData1(byte[] data1) + { + this.data1 = data1; + } + + public void setData2(byte[] data2) + { + this.data2 = data2; + } + + public byte[] getData1() + { + return data1; + } + + public byte[] getData2() + { + return data2; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/TooManyFilesOpen.java b/integration-tests/src/test/java/test/eclipse/store/various/TooManyFilesOpen.java new file mode 100644 index 000000000..08a3f6cfe --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/TooManyFilesOpen.java @@ -0,0 +1,62 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.file.Path; +import java.util.ArrayList; + +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class TooManyFilesOpen +{ + @TempDir + Path storagePath; + + @Test + @Disabled + void tooManyFilesOpen() + { + ArrayList root = new ArrayList<>(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, storagePath)) { + for (int i = 0; i < 100_000; i++) { + root.add(new Data("Data " + i)); + storageManager.store(root); + System.out.println(root.size()); + } + + } + + } + + + public static class Data + { + String name; + Lazy lazy; + + public Data(final String name) + { + super(); + this.name = name; + this.lazy = Lazy.Reference("Lazy content " + name); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/TransactionFileSizeTest.java b/integration-tests/src/test/java/test/eclipse/store/various/TransactionFileSizeTest.java new file mode 100644 index 000000000..86eea034e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/TransactionFileSizeTest.java @@ -0,0 +1,103 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.eclipse.store.storage.types.StorageConfiguration; +import org.eclipse.store.storage.types.StorageDataFileEvaluator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.datafaker.Faker; + +public class TransactionFileSizeTest +{ + + @TempDir + Path location; + + Faker faker = new Faker(); + + @Test + void setSizeConfigTest() + { + StorageDataFileEvaluator fileEvaluator = StorageDataFileEvaluator.New(1048576, 10485760, 0.75, true, 1025); + final StorageConfiguration cfg = StorageConfiguration.Builder() + .setStorageFileProvider(Storage.FileProvider(location)) + .setDataFileEvaluator(fileEvaluator) + .createConfiguration(); + + Customer customer = new Customer("first"); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(customer, cfg)) { + for (int i = 0; i < 500; i++) { + customer.setName(faker + .name() + .lastName()); + storageManager.store(customer); + } + } + File file = Path.of(location.toAbsolutePath() + .toString(), "channel_0", "transactions_0.sft") + .toFile(); + long length = file.length(); + //System.out.println(length); + assertTrue(length < 15_000); + } + + @Test + void callManualTest() + { + + Customer customer = new Customer("first"); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(customer, location)) { + for (int i = 0; i < 10; i++) { + customer.setName(faker + .name() + .lastName()); + storageManager.store(customer); + File file = Path.of(location.toAbsolutePath() + .toString(), "channel_0", "transactions_0.sft") + .toFile(); + long length = file.length(); + storageManager.issueTransactionsLogCleanup(); + long lengthAfterCleanup = file.length(); + assertTrue(length > lengthAfterCleanup); + } + } + } + + static class Customer + { + String name; + + public Customer(String name) + { + this.name = name; + } + + public void setName(String name) + { + this.name = name; + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/TruncationAfsTest.java b/integration-tests/src/test/java/test/eclipse/store/various/TruncationAfsTest.java new file mode 100644 index 000000000..1ee45ad28 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/TruncationAfsTest.java @@ -0,0 +1,89 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.nio.ByteBuffer; +import java.nio.file.Path; + +import org.eclipse.serializer.afs.types.ADirectory; +import org.eclipse.serializer.afs.types.AFile; +import org.eclipse.serializer.afs.types.AWritableFile; +import org.eclipse.store.afs.nio.types.NioFileSystem; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class TruncationAfsTest +{ + @TempDir + Path location; + + String VALUE = "Some long string value"; + + + @Test + void truncationWithAfs() + { + + TruncationRoot root = new TruncationRoot(); + root.setValue(VALUE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, location)) { + //do nothing + } + + final NioFileSystem fileSystem = NioFileSystem.New(); + + ADirectory aDirectory = fileSystem.ensureDirectory(location); + aDirectory.inventorize(); + + ADirectory aChannel = aDirectory.getDirectory("channel_0"); + //System.out.println(aChannel.toPathString()); + aChannel.inventorize(); + AFile file = aChannel.getFile("channel_0_1.dat"); + + AWritableFile aWritableFile = file.tryUseWriting(); + byte[] lines = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + ByteBuffer buffer = ByteBuffer.wrap(lines); + aWritableFile.writeBytes(buffer); + aWritableFile.close(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(location)) { + TruncationRoot localRoot = (TruncationRoot) storageManager.root(); + Assertions.assertEquals(VALUE, localRoot.getValue()); + } + + } + + + static class TruncationRoot + { + + String value; + + public String getValue() + { + return value; + } + + public void setValue(String value) + { + this.value = value; + } + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/UnloadedLazyTest.java b/integration-tests/src/test/java/test/eclipse/store/various/UnloadedLazyTest.java new file mode 100644 index 000000000..3fd2c52a4 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/UnloadedLazyTest.java @@ -0,0 +1,94 @@ +package test.eclipse.store.various; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Path; + +import org.eclipse.serializer.persistence.exceptions.PersistenceException; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class UnloadedLazyTest +{ + + @TempDir + Path location; + + private static PrintStream originalErr; + + @BeforeAll + static void setupErr() + { + originalErr = System.err; + System.setErr(new PrintStream(new OutputStream() + { + @Override + public void write(int b) + { + // Discard output + } + })); + } + + @AfterAll + static void restoreErr() + { + System.setErr(originalErr); + } + + + /** + * https://github.com/eclipse-serializer/serializer/issues/221 + * + * @param secondLocation + */ + @Test + public void saveDefaultLazySecondTimeTest(@TempDir final Path secondLocation) + { + final MyRoot myRoot = new MyRoot("Hello World"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(myRoot, this.location)) { + } + + myRoot.lazy.clear(); + + assertThrows(PersistenceException.class, () -> EmbeddedStorage.start(myRoot, secondLocation)); + + } + + + public static class MyRoot + { + Lazy lazy; + Integer number = 42; + + public MyRoot(final String content) + { + super(); + this.lazy = Lazy.Reference(content); + } + + } + +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/CheckedSetTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/CheckedSetTest.java new file mode 100644 index 000000000..ca79ada94 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/CheckedSetTest.java @@ -0,0 +1,434 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class CheckedSetTest +{ + @TempDir + Path workDir; + + private Set stateDataField; + + @Test + void checkedSetBasicTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("one"); + baseSet.add("two"); + baseSet.add("three"); + + stateDataField = Collections.checkedSet(baseSet, String.class); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + assertTrue(set.contains("three")); + } + } + + @Test + void checkedSetEmptyTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + } + } + + @Test + void checkedSetSingleElementTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("single"); + stateDataField = Collections.checkedSet(baseSet, String.class); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("single")); + } + } + + @Test + void checkedSetWithRemovalTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("alpha"); + baseSet.add("beta"); + baseSet.add("gamma"); + stateDataField = Collections.checkedSet(baseSet, String.class); + stateDataField.remove("beta"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(2, set.size()); + assertTrue(set.contains("alpha")); + assertFalse(set.contains("beta")); + assertTrue(set.contains("gamma")); + } + } + + @Test + void checkedSetWithLinkedHashSetTest() + { + LinkedHashSet baseSet = new LinkedHashSet<>(); + baseSet.add("first"); + baseSet.add("second"); + baseSet.add("third"); + stateDataField = Collections.checkedSet(baseSet, String.class); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("first")); + assertTrue(set.contains("second")); + assertTrue(set.contains("third")); + } + } + + @Test + void checkedSetWithTreeSetTest() + { + TreeSet baseSet = new TreeSet<>(); + baseSet.add("zebra"); + baseSet.add("apple"); + baseSet.add("mango"); + stateDataField = Collections.checkedSet(baseSet, String.class); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("zebra")); + assertTrue(set.contains("apple")); + assertTrue(set.contains("mango")); + } + } + + @Test + void checkedSetWithNullTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add(null); + baseSet.add("one"); + baseSet.add("two"); + stateDataField = Collections.checkedSet(baseSet, String.class); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains(null)); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + } + } + + @Test + void checkedSetClearTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("one"); + baseSet.add("two"); + stateDataField = Collections.checkedSet(baseSet, String.class); + stateDataField.clear(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + } + } + + @Test + void checkedSetAddAllTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.containsAll(Arrays.asList("a", "b", "c", "d", "e"))); + } + } + + @Test + void checkedSetRemoveAllTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + stateDataField.removeAll(Arrays.asList("b", "d")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertFalse(set.contains("b")); + assertTrue(set.contains("c")); + assertFalse(set.contains("d")); + assertTrue(set.contains("e")); + } + } + + @Test + void checkedSetRetainAllTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + stateDataField.retainAll(Arrays.asList("a", "c", "e")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertFalse(set.contains("b")); + assertTrue(set.contains("c")); + assertFalse(set.contains("d")); + assertTrue(set.contains("e")); + } + } + + @Test + void checkedSetLargeDatasetTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + + for (int i = 0; i < 100; i++) { + stateDataField.add("element_" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(100, set.size()); + for (int i = 0; i < 100; i++) { + assertTrue(set.contains("element_" + i)); + } + } + } + + @Test + void checkedSetIteratorTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.addAll(Arrays.asList("one", "two", "three", "four")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + int count = 0; + for (String element : set) { + assertNotNull(element); + count++; + } + assertEquals(4, count); + } + } + + @Test + void checkedSetWithIntegersTest() + { + Set intSet = Collections.checkedSet(new HashSet<>(), Integer.class); + intSet.add(1); + intSet.add(2); + intSet.add(3); + intSet.add(100); + intSet.add(-5); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.contains(1)); + assertTrue(set.contains(100)); + assertTrue(set.contains(-5)); + } + } + + @Test + void checkedSetToArrayTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.addAll(Arrays.asList("one", "two", "three")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Object[] array = set.toArray(); + assertEquals(3, array.length); + } + } + + @Test + void checkedSetDuplicateAttemptTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + assertTrue(stateDataField.add("element")); + assertFalse(stateDataField.add("element")); // duplicate should return false + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("element")); + } + } + + @Test + void checkedSetRemoveNonExistentTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.add("one"); + assertFalse(stateDataField.remove("two")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + } + } + + @Test + void checkedSetMaxSizeTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + + for (int i = 0; i < 1000; i++) { + stateDataField.add("item_" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1000, set.size()); + assertTrue(set.contains("item_0")); + assertTrue(set.contains("item_999")); + } + } + + @Test + void checkedSetContainsAllTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.containsAll(Arrays.asList("a", "b"))); + assertFalse(set.containsAll(Arrays.asList("a", "e"))); + } + } + + @Test + void checkedSetEmptyAfterAddAndRemoveTest() + { + stateDataField = Collections.checkedSet(new HashSet<>(), String.class); + stateDataField.add("temporary"); + stateDataField.remove("temporary"); + assertTrue(stateDataField.isEmpty()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + } + } + + @Test + void checkedSetWithLongTest() + { + Set longSet = Collections.checkedSet(new HashSet<>(), Long.class); + longSet.add(1L); + longSet.add(100L); + longSet.add(1000000L); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(longSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains(1L)); + assertTrue(set.contains(1000000L)); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/EmptySetTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/EmptySetTest.java new file mode 100644 index 000000000..0421c13a5 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/EmptySetTest.java @@ -0,0 +1,356 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class EmptySetTest +{ + @TempDir + Path workDir; + + private Set stateDataField; + + @Test + void emptySetBasicTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + } + } + + @Test + void emptySetIsEmptyTest() + { + stateDataField = Collections.emptySet(); + assertTrue(stateDataField.isEmpty()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + } + } + + @Test + void emptySetSizeTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(0, set.size()); + } + } + + @Test + void emptySetContainsTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertFalse(set.contains("anything")); + assertFalse(set.contains(null)); + assertFalse(set.contains("")); + } + } + + @Test + void emptySetIteratorTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + int count = 0; + for (String element : set) { + count++; + } + assertEquals(0, count); + } + } + + @Test + void emptySetToArrayTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Object[] array = set.toArray(); + assertEquals(0, array.length); + } + } + + @Test + void emptySetContainsAllEmptyTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.containsAll(Collections.emptyList())); + } + } + + @Test + void emptySetContainsAllNonEmptyTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertFalse(set.containsAll(Arrays.asList("element"))); + } + } + + @Test + void emptySetEqualsTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(Collections.emptySet(), set); + assertEquals(new HashSet<>(), set); + } + } + + @Test + void emptySetHashCodeTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(0, set.hashCode()); + } + } + + @Test + void emptySetToStringTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertNotNull(set.toString()); + assertEquals("[]", set.toString()); + } + } + + @Test + void emptySetIntegerTest() + { + Set intSet = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertFalse(set.contains(0)); + assertFalse(set.contains(1)); + } + } + + @Test + void emptySetLongTest() + { + Set longSet = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(longSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertFalse(set.contains(0L)); + } + } + + @Test + void emptySetDoubleTest() + { + Set doubleSet = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(doubleSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertFalse(set.contains(0.0)); + } + } + + @Test + void emptySetMultipleInstancesTest() + { + Set emptySet1 = Collections.emptySet(); + Set emptySet2 = Collections.emptySet(); + + // Both should be equal + assertEquals(emptySet1, emptySet2); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(emptySet1, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(emptySet2, set); + } + } + + @Test + void emptySetNotEqualsNonEmptyTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Set nonEmptySet = new HashSet<>(); + nonEmptySet.add("item"); + assertNotEquals(nonEmptySet, set); + } + } + + @Test + void emptySetStreamTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(0, set.stream().count()); + } + } + + @Test + void emptySetCompareWithNewHashSetTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Set newHashSet = new HashSet<>(); + assertEquals(newHashSet, set); + assertEquals(newHashSet.size(), set.size()); + } + } + + @Test + void emptySetIteratorHasNextTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Iterator iterator = set.iterator(); + assertFalse(iterator.hasNext()); + } + } + + @Test + void emptySetForEachTest() + { + stateDataField = Collections.emptySet(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + final int[] count = {0}; + set.forEach(element -> count[0]++); + assertEquals(0, count[0]); + } + } + + @Test + void emptySetTypedTest() + { + Set typedEmptySet = Collections.emptySet(); + stateDataField = typedEmptySet; + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/ListOfTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/ListOfTest.java new file mode 100644 index 000000000..db87bda9c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/ListOfTest.java @@ -0,0 +1,288 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Round-trip tests for java.util.List.of(...) (JDK 9). + *

+ * Backing types observed on JDK 17: + * List.of() -> java.util.ImmutableCollections$ListN + * List.of(x) -> java.util.ImmutableCollections$List12 + * List.of(x, y) -> java.util.ImmutableCollections$List12 + * List.of(x, y, z, ...) -> java.util.ImmutableCollections$ListN + *

+ * Eclipse Serializer ships BinaryHandlerImmutableCollectionsList12 but no + * handler for ListN, so every size != 1,2 hits a generic fallback or fails. + * The empty List.of() is also ListN, so it is *not* covered by the List12 handler. + *

+ * Each test asserts a strict round-trip: the reloaded instance must equal + * the original by value, preserve iteration order, and remain immutable + * (mutating it must still throw UnsupportedOperationException). + */ +public class ListOfTest +{ + @TempDir + Path workDir; + + private List stateDataField; + + @Test + void listOfEmptyTest() + { + // Empty List.of() returns ListN, NOT List12 — easy to overlook. + stateDataField = List.of(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertNotNull(list); + assertTrue(list.isEmpty()); + assertEquals(0, list.size()); + assertEquals(stateDataField, list); + assertThrows(UnsupportedOperationException.class, () -> list.add("x")); + } + } + + @Test + void listOfSingleElementTest() + { + stateDataField = List.of("only"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(1, list.size()); + assertEquals("only", list.get(0)); + assertEquals(stateDataField, list); + assertThrows(UnsupportedOperationException.class, () -> list.add("x")); + } + } + + @Test + void listOfTwoElementsTest() + { + stateDataField = List.of("a", "b"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(2, list.size()); + assertEquals("a", list.get(0)); + assertEquals("b", list.get(1)); + assertEquals(stateDataField, list); + assertThrows(UnsupportedOperationException.class, () -> list.set(0, "x")); + } + } + + @Test + void listOfThreeElementsTest() + { + // Size 3 crosses the List12 -> ListN boundary. + stateDataField = List.of("a", "b", "c"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(3, list.size()); + assertEquals(List.of("a", "b", "c"), list); + assertEquals(stateDataField, list); + assertThrows(UnsupportedOperationException.class, () -> list.add("d")); + } + } + + @Test + void listOfFiveElementsTest() + { + stateDataField = List.of("a", "b", "c", "d", "e"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(5, list.size()); + for (int i = 0; i < 5; i++) { + assertEquals(stateDataField.get(i), list.get(i)); + } + assertEquals(stateDataField, list); + assertThrows(UnsupportedOperationException.class, () -> list.remove(0)); + } + } + + @Test + void listOfHundredElementsTest() + { + String[] arr = new String[100]; + for (int i = 0; i < 100; i++) { + arr[i] = "v_" + i; + } + stateDataField = List.of(arr); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(100, list.size()); + assertEquals("v_0", list.get(0)); + assertEquals("v_99", list.get(99)); + assertEquals(stateDataField, list); + } + } + + @Test + void listOfWithIntegersTest() + { + List ints = List.of(1, 2, 3, 100, -5, Integer.MIN_VALUE, Integer.MAX_VALUE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ints, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(ints.size(), list.size()); + for (int i = 0; i < ints.size(); i++) { + assertEquals(ints.get(i), list.get(i)); + } + } + } + + @Test + void listOfWithDuplicatesPreservedTest() + { + // List.of permits duplicates (only Set.of forbids them). + stateDataField = List.of("dup", "dup", "dup"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(3, list.size()); + assertEquals(List.of("dup", "dup", "dup"), list); + } + } + + @Test + void listOfNestedTest() + { + List> nested = List.of( + List.of("a", "b"), + List.of("c", "d", "e"), + List.of() + ); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(nested, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List> list = (List>) storageManager.root(); + assertEquals(3, list.size()); + assertEquals(List.of("a", "b"), list.get(0)); + assertEquals(List.of("c", "d", "e"), list.get(1)); + assertTrue(list.get(2).isEmpty()); + assertEquals(nested, list); + } + } + + @Test + void listOfHashCodeAndEqualsTest() + { + stateDataField = List.of("alpha", "beta", "gamma"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(stateDataField.hashCode(), list.hashCode()); + assertEquals(stateDataField, list); + assertEquals(list, new ArrayList<>(List.of("alpha", "beta", "gamma"))); + } + } + + @Test + void listOfIterationOrderTest() + { + stateDataField = List.of("first", "second", "third", "fourth", "fifth"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + List collected = new ArrayList<>(); + list.forEach(collected::add); + assertEquals(List.of("first", "second", "third", "fourth", "fifth"), collected); + } + } + + @Test + void listOfRejectsAddAfterReloadTest() + { + // Strong type-preservation check: if the reloaded list silently + // becomes a mutable ArrayList, this assertion fires. + stateDataField = List.of("x", "y", "z"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertThrows(UnsupportedOperationException.class, () -> list.add("new")); + assertThrows(UnsupportedOperationException.class, () -> list.remove(0)); + assertThrows(UnsupportedOperationException.class, () -> list.set(0, "new")); + assertThrows(UnsupportedOperationException.class, () -> list.clear()); + } + } + + @Test + void listOfFromCopyOfTest() + { + // List.copyOf also returns ImmutableCollections$ListN. + ArrayList src = new ArrayList<>(Arrays.asList("p", "q", "r")); + List immutable = List.copyOf(src); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(immutable, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(3, list.size()); + assertEquals(immutable, list); + assertThrows(UnsupportedOperationException.class, () -> list.add("s")); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/MapOfTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/MapOfTest.java new file mode 100644 index 000000000..947e3f5dd --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/MapOfTest.java @@ -0,0 +1,320 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Round-trip tests for java.util.Map.of(...), Map.ofEntries(...) and Map.entry(...) (JDK 9). + *

+ * Backing types observed on JDK 17: + * Map.of() -> java.util.ImmutableCollections$MapN + * Map.of(k, v) -> java.util.ImmutableCollections$Map1 + * Map.of(k1,v1, k2,v2) -> java.util.ImmutableCollections$MapN + * Map.entry(k, v) -> java.util.KeyValueHolder + *

+ * Unlike List.of and Set.of, Eclipse Serializer ships **no** dedicated + * handler for Map.of variants and **no** handler for KeyValueHolder. + * These tests therefore intentionally exercise the generic / fallback path + * and check whether contents, equality and immutability survive a round trip. + */ +public class MapOfTest +{ + @TempDir + Path workDir; + + private Map stateDataField; + + @Test + void mapOfEmptyTest() + { + stateDataField = Map.of(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertNotNull(map); + assertTrue(map.isEmpty()); + assertEquals(0, map.size()); + assertEquals(stateDataField, map); + assertThrows(UnsupportedOperationException.class, () -> map.put("k", "v")); + } + } + + @Test + void mapOfSingleEntryTest() + { + // Single-entry Map.of returns Map1. + stateDataField = Map.of("k1", "v1"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(1, map.size()); + assertEquals("v1", map.get("k1")); + assertEquals(stateDataField, map); + assertThrows(UnsupportedOperationException.class, () -> map.put("k2", "v2")); + } + } + + @Test + void mapOfTwoEntriesTest() + { + // Two-entry Map.of crosses Map1 -> MapN. + stateDataField = Map.of("k1", "v1", "k2", "v2"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(2, map.size()); + assertEquals("v1", map.get("k1")); + assertEquals("v2", map.get("k2")); + assertEquals(stateDataField, map); + } + } + + @Test + void mapOfFiveEntriesTest() + { + stateDataField = Map.of( + "k1", "v1", + "k2", "v2", + "k3", "v3", + "k4", "v4", + "k5", "v5" + ); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(5, map.size()); + for (int i = 1; i <= 5; i++) { + assertEquals("v" + i, map.get("k" + i)); + } + assertEquals(stateDataField, map); + assertThrows(UnsupportedOperationException.class, () -> map.put("k6", "v6")); + } + } + + @Test + void mapOfTenEntriesTest() + { + // Map.of has explicit overloads up to 10 entries. + stateDataField = Map.of( + "k1", "v1", + "k2", "v2", + "k3", "v3", + "k4", "v4", + "k5", "v5", + "k6", "v6", + "k7", "v7", + "k8", "v8", + "k9", "v9", + "k10", "v10" + ); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(10, map.size()); + for (int i = 1; i <= 10; i++) { + assertEquals("v" + i, map.get("k" + i)); + } + assertEquals(stateDataField, map); + } + } + + @Test + void mapOfEntriesLargeTest() + { + // Map.ofEntries() with > 10 entries — the only way to build big immutable maps. + @SuppressWarnings("unchecked") + Map.Entry[] entries = new Map.Entry[100]; + for (int i = 0; i < 100; i++) { + entries[i] = Map.entry("k" + i, i); + } + Map bigMap = Map.ofEntries(entries); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(bigMap, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(100, map.size()); + for (int i = 0; i < 100; i++) { + assertEquals(Integer.valueOf(i), map.get("k" + i)); + } + assertEquals(bigMap, map); + assertThrows(UnsupportedOperationException.class, () -> map.put("k100", 100)); + } + } + + @Test + @Disabled("https://github.com/microstream-one/internal/issues/54") + void mapEntryStandaloneTest() + { + // Map.entry(...) returns KeyValueHolder — its own immutable Map.Entry. + Map.Entry entry = Map.entry("foo", 42); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(entry, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map.Entry reloaded = (Map.Entry) storageManager.root(); + assertNotNull(reloaded); + assertEquals("foo", reloaded.getKey()); + assertEquals(Integer.valueOf(42), reloaded.getValue()); + assertEquals(entry, reloaded); + assertEquals(entry.hashCode(), reloaded.hashCode()); + // KeyValueHolder is immutable. + assertThrows(UnsupportedOperationException.class, () -> reloaded.setValue(99)); + } + } + + @Test + void mapEntryWithNullValueTest() + { + // Map.entry forbids nulls — sanity check that the JDK constructor still + // rejects them (not strictly a serializer test, but useful to surface + // any wrapper that *would* be acceptable here). + assertThrows(NullPointerException.class, () -> Map.entry("k", null)); + assertThrows(NullPointerException.class, () -> Map.entry(null, "v")); + } + + @Test + void mapOfWithIntegersTest() + { + Map ints = Map.of( + 1, 11, + 2, 22, + 3, 33, + -1, -11, + Integer.MIN_VALUE, Integer.MAX_VALUE + ); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ints, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(ints.size(), map.size()); + assertEquals(ints, map); + assertEquals(Integer.valueOf(33), map.get(3)); + assertEquals(Integer.valueOf(Integer.MAX_VALUE), map.get(Integer.MIN_VALUE)); + } + } + + @Test + void mapOfNestedTest() + { + Map> nested = Map.of( + "a", Map.of("x", 1, "y", 2), + "b", Map.of("z", 3), + "c", Map.of() + ); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(nested, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map> map = (Map>) storageManager.root(); + assertEquals(3, map.size()); + assertEquals(Map.of("x", 1, "y", 2), map.get("a")); + assertEquals(Map.of("z", 3), map.get("b")); + assertTrue(map.get("c").isEmpty()); + assertEquals(nested, map); + } + } + + @Test + void mapOfHashCodeAndEqualsTest() + { + stateDataField = Map.of("k1", "v1", "k2", "v2", "k3", "v3"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(stateDataField.hashCode(), map.hashCode()); + assertEquals(stateDataField, map); + + HashMap reference = new HashMap<>(); + reference.put("k1", "v1"); + reference.put("k2", "v2"); + reference.put("k3", "v3"); + assertEquals(reference, map); + } + } + + @Test + void mapOfRejectsMutationAfterReloadTest() + { + // If the reloaded map silently becomes a mutable HashMap, these fail. + stateDataField = Map.of("a", "1", "b", "2", "c", "3"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertThrows(UnsupportedOperationException.class, () -> map.put("d", "4")); + assertThrows(UnsupportedOperationException.class, () -> map.remove("a")); + assertThrows(UnsupportedOperationException.class, () -> map.clear()); + assertThrows(UnsupportedOperationException.class, () -> map.putAll(Map.of("e", "5"))); + } + } + + @Test + void mapCopyOfTest() + { + // Map.copyOf returns ImmutableCollections.MapN. + HashMap src = new HashMap<>(); + src.put("k1", "v1"); + src.put("k2", "v2"); + src.put("k3", "v3"); + Map immutable = Map.copyOf(src); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(immutable, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map map = (Map) storageManager.root(); + assertEquals(3, map.size()); + assertEquals(immutable, map); + assertThrows(UnsupportedOperationException.class, () -> map.put("k4", "v4")); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/SetFromMapTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/SetFromMapTest.java new file mode 100644 index 000000000..97560d524 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/SetFromMapTest.java @@ -0,0 +1,468 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class SetFromMapTest +{ + @TempDir + Path workDir; + + + private Set stateDataField; + + //https://github.com/eclipse-serializer/serializer/pull/138 + @Test + void setFromMapTest() + { + + LinkedHashMap map = new LinkedHashMap<>(5); + + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("one"); + stateDataField.add("two"); + stateDataField.add("three"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + //compare two sets + assertIterableEquals(stateDataField, set); + } + + } + + @Test + void setFromMapEmptyTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapWithRemovalTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("one"); + stateDataField.add("two"); + stateDataField.add("three"); + stateDataField.remove("two"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(2, set.size()); + assertTrue(set.contains("one")); + assertFalse(set.contains("two")); + assertTrue(set.contains("three")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromHashMapTest() + { + HashMap map = new HashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("alpha"); + stateDataField.add("beta"); + stateDataField.add("gamma"); + stateDataField.add("delta"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(4, set.size()); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromTreeMapTest() + { + TreeMap map = new TreeMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("zebra"); + stateDataField.add("apple"); + stateDataField.add("mango"); + stateDataField.add("banana"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(4, set.size()); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromConcurrentHashMapTest() + { + ConcurrentHashMap map = new ConcurrentHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("concurrent1"); + stateDataField.add("concurrent2"); + stateDataField.add("concurrent3"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapWithDuplicateAttemptTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + assertTrue(stateDataField.add("element")); + assertFalse(stateDataField.add("element")); // duplicate should return false + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("element")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapLargeDatasetTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + + for (int i = 0; i < 100; i++) { + stateDataField.add("element_" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(100, set.size()); + assertEquals(stateDataField, set); + + for (int i = 0; i < 100; i++) { + assertTrue(set.contains("element_" + i)); + } + } + } + + @Test + void setFromMapClearTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("one"); + stateDataField.add("two"); + stateDataField.clear(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapWithNullTest() + { + HashMap map = new HashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add(null); + stateDataField.add("one"); + stateDataField.add("two"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains(null)); + assertTrue(set.contains("one")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapSingleElementTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("single"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("single")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapAddAllTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.containsAll(Arrays.asList("a", "b", "c", "d", "e"))); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapRemoveAllTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + stateDataField.removeAll(Arrays.asList("b", "d")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertFalse(set.contains("b")); + assertTrue(set.contains("c")); + assertFalse(set.contains("d")); + assertTrue(set.contains("e")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapRetainAllTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + stateDataField.retainAll(Arrays.asList("a", "c", "e")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertFalse(set.contains("b")); + assertTrue(set.contains("c")); + assertFalse(set.contains("d")); + assertTrue(set.contains("e")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapIteratorRemovalTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.addAll(Arrays.asList("one", "two", "three", "four")); + + Iterator iterator = stateDataField.iterator(); + while (iterator.hasNext()) { + String element = iterator.next(); + if (element.equals("two")) { + iterator.remove(); + } + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertFalse(set.contains("two")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapContainsAllTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.containsAll(Arrays.asList("a", "b"))); + assertFalse(set.containsAll(Arrays.asList("a", "e"))); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapToArrayTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.addAll(Arrays.asList("one", "two", "three")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Object[] array = set.toArray(); + assertEquals(3, array.length); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapWithIntegersTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + Set intSet = Collections.newSetFromMap(map); + intSet.add(1); + intSet.add(2); + intSet.add(3); + intSet.add(100); + intSet.add(-5); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.contains(1)); + assertTrue(set.contains(100)); + assertTrue(set.contains(-5)); + assertEquals(intSet, set); + } + } + + @Test + void setFromMapMaxSizeTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + + for (int i = 0; i < 1000; i++) { + stateDataField.add("item_" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1000, set.size()); + assertTrue(set.contains("item_0")); + assertTrue(set.contains("item_999")); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapRemoveNonExistentTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("one"); + assertFalse(stateDataField.remove("two")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertEquals(stateDataField, set); + } + } + + @Test + void setFromMapEmptyAfterAddAndRemoveTest() + { + LinkedHashMap map = new LinkedHashMap<>(); + stateDataField = Collections.newSetFromMap(map); + stateDataField.add("temporary"); + stateDataField.remove("temporary"); + assertTrue(stateDataField.isEmpty()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + assertEquals(stateDataField, set); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/SetOfTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/SetOfTest.java new file mode 100644 index 000000000..bf8f6bc94 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/SetOfTest.java @@ -0,0 +1,251 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Round-trip tests for java.util.Set.of(...) (JDK 9). + *

+ * Backing types observed on JDK 17: + * Set.of() -> java.util.ImmutableCollections$SetN + * Set.of(x) -> java.util.ImmutableCollections$Set12 + * Set.of(x, y) -> java.util.ImmutableCollections$Set12 + * Set.of(x, y, z, ...) -> java.util.ImmutableCollections$SetN + *

+ * Eclipse Serializer ships BinaryHandlerImmutableCollectionsSet12 but no + * handler for SetN. Empty Set.of() is SetN, not Set12. + *

+ * Iteration order in SetN is *deliberately randomized* per JVM via SALT, + * so we only assert membership and size, never order. + */ +public class SetOfTest +{ + @TempDir + Path workDir; + + private Set stateDataField; + + @Test + void setOfEmptyTest() + { + // Empty Set.of() returns SetN. + stateDataField = Set.of(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertNotNull(set); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + assertEquals(stateDataField, set); + assertThrows(UnsupportedOperationException.class, () -> set.add("x")); + } + } + + @Test + void setOfSingleTest() + { + stateDataField = Set.of("only"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("only")); + assertEquals(stateDataField, set); + assertThrows(UnsupportedOperationException.class, () -> set.add("x")); + } + } + + @Test + void setOfTwoTest() + { + stateDataField = Set.of("a", "b"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(2, set.size()); + assertTrue(set.contains("a")); + assertTrue(set.contains("b")); + assertEquals(stateDataField, set); + assertThrows(UnsupportedOperationException.class, () -> set.remove("a")); + } + } + + @Test + void setOfThreeTest() + { + // Size 3 crosses the Set12 -> SetN boundary. + stateDataField = Set.of("a", "b", "c"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertTrue(set.contains("b")); + assertTrue(set.contains("c")); + assertEquals(stateDataField, set); + assertThrows(UnsupportedOperationException.class, () -> set.add("d")); + } + } + + @Test + void setOfFiveTest() + { + stateDataField = Set.of("a", "b", "c", "d", "e"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.containsAll(stateDataField)); + assertEquals(stateDataField, set); + } + } + + @Test + void setOfHundredTest() + { + String[] arr = new String[100]; + for (int i = 0; i < 100; i++) { + arr[i] = "v_" + i; + } + stateDataField = Set.of(arr); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(100, set.size()); + assertTrue(set.contains("v_0")); + assertTrue(set.contains("v_99")); + assertEquals(stateDataField, set); + } + } + + @Test + void setOfWithIntegersTest() + { + Set ints = Set.of(1, 2, 3, 100, -5, Integer.MIN_VALUE, Integer.MAX_VALUE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ints, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(ints.size(), set.size()); + assertEquals(ints, set); + } + } + + @Test + void setOfNestedTest() + { + Set> nested = Set.of( + Set.of("a", "b"), + Set.of("c", "d", "e"), + Set.of() + ); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(nested, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set> set = (Set>) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains(Set.of("a", "b"))); + assertTrue(set.contains(Set.of("c", "d", "e"))); + assertTrue(set.contains(Set.of())); + assertEquals(nested, set); + } + } + + @Test + void setOfHashCodeAndEqualsTest() + { + stateDataField = Set.of("alpha", "beta", "gamma"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(stateDataField.hashCode(), set.hashCode()); + assertEquals(stateDataField, set); + assertEquals(set, new HashSet<>(Arrays.asList("alpha", "beta", "gamma"))); + } + } + + @Test + void setOfRejectsMutationAfterReloadTest() + { + // Type-preservation: if the reloaded set silently becomes a mutable + // HashSet, these assertions fail. + stateDataField = Set.of("x", "y", "z"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertThrows(UnsupportedOperationException.class, () -> set.add("new")); + assertThrows(UnsupportedOperationException.class, () -> set.remove("x")); + assertThrows(UnsupportedOperationException.class, () -> set.clear()); + assertThrows(UnsupportedOperationException.class, () -> set.addAll(Arrays.asList("p", "q"))); + } + } + + @Test + void setOfFromCopyOfTest() + { + // Set.copyOf also routes through ImmutableCollections. + HashSet src = new HashSet<>(Arrays.asList("p", "q", "r")); + Set immutable = Set.copyOf(src); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(immutable, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertEquals(immutable, set); + assertThrows(UnsupportedOperationException.class, () -> set.add("s")); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/SingletonSetTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/SingletonSetTest.java new file mode 100644 index 000000000..c5d438ca5 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/SingletonSetTest.java @@ -0,0 +1,359 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class SingletonSetTest +{ + @TempDir + Path workDir; + + private Set stateDataField; + + @Test + void singletonSetBasicTest() + { + stateDataField = Collections.singleton("onlyOne"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("onlyOne")); + } + } + + @Test + void singletonSetWithStringTest() + { + stateDataField = Collections.singleton("test"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("test")); + assertFalse(set.contains("other")); + } + } + + @Test + void singletonSetWithIntegerTest() + { + Set intSet = Collections.singleton(42); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(42)); + assertFalse(set.contains(0)); + } + } + + @Test + void singletonSetWithNullTest() + { + stateDataField = Collections.singleton(null); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(null)); + } + } + + @Test + void singletonSetContainsTest() + { + stateDataField = Collections.singleton("element"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.contains("element")); + assertFalse(set.contains("nonexistent")); + } + } + + @Test + void singletonSetIsEmptyTest() + { + stateDataField = Collections.singleton("item"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertFalse(set.isEmpty()); + } + } + + @Test + void singletonSetIteratorTest() + { + stateDataField = Collections.singleton("single"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + int count = 0; + for (String element : set) { + assertEquals("single", element); + count++; + } + assertEquals(1, count); + } + } + + @Test + void singletonSetToArrayTest() + { + stateDataField = Collections.singleton("value"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Object[] array = set.toArray(); + assertEquals(1, array.length); + assertEquals("value", array[0]); + } + } + + @Test + void singletonSetContainsAllTest() + { + stateDataField = Collections.singleton("element"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.containsAll(Collections.singletonList("element"))); + assertFalse(set.containsAll(Arrays.asList("element", "other"))); + } + } + + @Test + void singletonSetEqualsTest() + { + stateDataField = Collections.singleton("item"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Set expected = Collections.singleton("item"); + assertEquals(expected, set); + } + } + + @Test + void singletonSetHashCodeTest() + { + stateDataField = Collections.singleton("test"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertNotNull(set.hashCode()); + } + } + + @Test + void singletonSetToStringTest() + { + stateDataField = Collections.singleton("value"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertNotNull(set.toString()); + assertTrue(set.toString().contains("value")); + } + } + + @Test + void singletonSetWithLongStringTest() + { + String longString = "This is a very long string that contains multiple words and characters"; + stateDataField = Collections.singleton(longString); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(longString)); + } + } + + @Test + void singletonSetWithNegativeIntegerTest() + { + Set intSet = Collections.singleton(-999); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(-999)); + } + } + + @Test + void singletonSetWithZeroTest() + { + Set intSet = Collections.singleton(0); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(0)); + } + } + + @Test + void singletonSetWithLongTest() + { + Set longSet = Collections.singleton(123456789L); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(longSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(123456789L)); + } + } + + @Test + void singletonSetWithDoubleTest() + { + Set doubleSet = Collections.singleton(3.14159); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(doubleSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains(3.14159)); + } + } + + @Test + void singletonSetWithEmptyStringTest() + { + stateDataField = Collections.singleton(""); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("")); + } + } + + @Test + void singletonSetWithSpecialCharactersTest() + { + stateDataField = Collections.singleton("!@#$%^&*()_+-=[]{}|;:',.<>?/~`"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("!@#$%^&*()_+-=[]{}|;:',.<>?/~`")); + } + } + + @Test + void singletonSetWithUnicodeTest() + { + stateDataField = Collections.singleton("Hello 世界 🌍"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("Hello 世界 🌍")); + } + } + + @Test + void singletonSetCompareToOtherSetTest() + { + stateDataField = Collections.singleton("item"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Set otherSet = new HashSet<>(); + otherSet.add("item"); + assertEquals(otherSet, set); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/StreamToListTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/StreamToListTest.java new file mode 100644 index 000000000..c48084f46 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/StreamToListTest.java @@ -0,0 +1,239 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Round-trip tests for the unmodifiable lists produced by: + * Stream.toList() (JDK 16) + * Collectors.toUnmodifiableList() (JDK 10) + * Collectors.toUnmodifiableSet() (JDK 10) + * Collectors.toUnmodifiableMap(...) (JDK 10) + *

+ * On JDK 17 every Stream.toList() invocation returns + * java.util.ImmutableCollections$ListN regardless of size — even for + * 0 and 1 element. The List12 handler therefore never applies and these + * collections always go through the generic / fallback path. + *

+ * The reloaded collection must remain unmodifiable: a successful add() + * means the type was silently downgraded to ArrayList/HashSet/HashMap + * during persistence, which is a behavior bug. + */ +public class StreamToListTest +{ + @TempDir + Path workDir; + + @Test + void streamToListEmptyTest() + { + // Even Stream.of().toList() returns ListN, not List0/List12. + List empty = Stream.of().toList(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(empty, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertNotNull(list); + assertTrue(list.isEmpty()); + assertEquals(empty, list); + assertThrows(UnsupportedOperationException.class, () -> list.add("x")); + } + } + + @Test + void streamToListSingleTest() + { + List single = Stream.of("only").toList(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(single, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(1, list.size()); + assertEquals("only", list.get(0)); + assertEquals(single, list); + assertThrows(UnsupportedOperationException.class, () -> list.add("x")); + } + } + + @Test + void streamToListManyTest() + { + List ints = IntStream.rangeClosed(1, 50).boxed().toList(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ints, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(50, list.size()); + for (int i = 0; i < 50; i++) { + assertEquals(Integer.valueOf(i + 1), list.get(i)); + } + assertEquals(ints, list); + assertThrows(UnsupportedOperationException.class, () -> list.add(99)); + } + } + + @Test + void streamToListPreservesOrderTest() + { + List ordered = Stream.of("first", "second", "third", "fourth").toList(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ordered, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(List.of("first", "second", "third", "fourth"), list); + } + } + + @Test + void streamToListWithNullsTest() + { + // Stream.toList() permits nulls (unlike List.of). This is one of the + // few documented behavioural differences and worth a round-trip check. + List withNulls = Stream.of("a", null, "b", null, "c").toList(); + assertEquals(5, withNulls.size()); + assertNull(withNulls.get(1)); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(withNulls, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List list = (List) storageManager.root(); + assertEquals(5, list.size()); + assertEquals("a", list.get(0)); + assertNull(list.get(1)); + assertEquals("b", list.get(2)); + assertNull(list.get(3)); + assertEquals("c", list.get(4)); + assertThrows(UnsupportedOperationException.class, () -> list.add("x")); + } + } + + @Test + void collectorsToUnmodifiableListTest() + { + List list = Stream.of("a", "b", "c", "d") + .collect(Collectors.toUnmodifiableList()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List reloaded = (List) storageManager.root(); + assertEquals(4, reloaded.size()); + assertEquals(list, reloaded); + assertThrows(UnsupportedOperationException.class, () -> reloaded.add("e")); + } + } + + @Test + void collectorsToUnmodifiableSetTest() + { + Set set = Stream.of("a", "b", "c", "d") + .collect(Collectors.toUnmodifiableSet()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(set, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set reloaded = (Set) storageManager.root(); + assertEquals(4, reloaded.size()); + assertEquals(set, reloaded); + assertTrue(reloaded.containsAll(Arrays.asList("a", "b", "c", "d"))); + assertThrows(UnsupportedOperationException.class, () -> reloaded.add("e")); + } + } + + @Test + void collectorsToUnmodifiableMapTest() + { + Map map = Stream.of("a", "b", "c", "d") + .collect(Collectors.toUnmodifiableMap(s -> s, String::length)); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Map reloaded = (Map) storageManager.root(); + assertEquals(4, reloaded.size()); + assertEquals(map, reloaded); + assertEquals(Integer.valueOf(1), reloaded.get("a")); + assertThrows(UnsupportedOperationException.class, () -> reloaded.put("e", 1)); + } + } + + @Test + void streamToListEqualsArrayListTest() + { + // Defensive: equality against an ArrayList of the same content is the + // contractual definition of List#equals. + List immutable = Stream.of("p", "q", "r", "s").toList(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(immutable, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + List reloaded = (List) storageManager.root(); + assertEquals(new ArrayList<>(Arrays.asList("p", "q", "r", "s")), reloaded); + assertEquals(immutable.hashCode(), reloaded.hashCode()); + } + } + + @Test + void streamToListInsideHolderTest() + { + // A more realistic shape: an unmodifiable list embedded as a field + // inside a domain object that is the storage root. + Holder root = new Holder(); + root.label = "demo"; + root.items = Stream.of("a", "b", "c", "d", "e").toList(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Holder reloaded = (Holder) storageManager.root(); + assertEquals("demo", reloaded.label); + assertEquals(5, reloaded.items.size()); + assertEquals(List.of("a", "b", "c", "d", "e"), reloaded.items); + assertThrows(UnsupportedOperationException.class, () -> reloaded.items.add("f")); + } + } + + static class Holder + { + String label; + List items; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/SynchronizedSetTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/SynchronizedSetTest.java new file mode 100644 index 000000000..3d2df51fe --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/SynchronizedSetTest.java @@ -0,0 +1,415 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class SynchronizedSetTest +{ + @TempDir + Path workDir; + + private Set stateDataField; + + @Test + void synchronizedSetBasicTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("one"); + baseSet.add("two"); + baseSet.add("three"); + + stateDataField = Collections.synchronizedSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + assertTrue(set.contains("three")); + } + } + + @Test + void synchronizedSetEmptyTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + } + } + + @Test + void synchronizedSetSingleElementTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("single"); + stateDataField = Collections.synchronizedSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("single")); + } + } + + @Test + void synchronizedSetWithRemovalTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("alpha"); + baseSet.add("beta"); + baseSet.add("gamma"); + stateDataField = Collections.synchronizedSet(baseSet); + stateDataField.remove("beta"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(2, set.size()); + assertTrue(set.contains("alpha")); + assertFalse(set.contains("beta")); + assertTrue(set.contains("gamma")); + } + } + + @Test + void synchronizedSetWithLinkedHashSetTest() + { + LinkedHashSet baseSet = new LinkedHashSet<>(); + baseSet.add("first"); + baseSet.add("second"); + baseSet.add("third"); + stateDataField = Collections.synchronizedSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("first")); + assertTrue(set.contains("second")); + assertTrue(set.contains("third")); + } + } + + @Test + void synchronizedSetWithTreeSetTest() + { + TreeSet baseSet = new TreeSet<>(); + baseSet.add("zebra"); + baseSet.add("apple"); + baseSet.add("mango"); + stateDataField = Collections.synchronizedSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("zebra")); + assertTrue(set.contains("apple")); + assertTrue(set.contains("mango")); + } + } + + @Test + void synchronizedSetWithNullTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add(null); + baseSet.add("one"); + baseSet.add("two"); + stateDataField = Collections.synchronizedSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains(null)); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + } + } + + @Test + void synchronizedSetClearTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("one"); + baseSet.add("two"); + stateDataField = Collections.synchronizedSet(baseSet); + stateDataField.clear(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + } + } + + @Test + void synchronizedSetAddAllTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.containsAll(Arrays.asList("a", "b", "c", "d", "e"))); + } + } + + @Test + void synchronizedSetRemoveAllTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + stateDataField.removeAll(Arrays.asList("b", "d")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertFalse(set.contains("b")); + assertTrue(set.contains("c")); + assertFalse(set.contains("d")); + assertTrue(set.contains("e")); + } + } + + @Test + void synchronizedSetRetainAllTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d", "e")); + stateDataField.retainAll(Arrays.asList("a", "c", "e")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertFalse(set.contains("b")); + assertTrue(set.contains("c")); + assertFalse(set.contains("d")); + assertTrue(set.contains("e")); + } + } + + @Test + void synchronizedSetLargeDatasetTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + + for (int i = 0; i < 100; i++) { + stateDataField.add("element_" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(100, set.size()); + for (int i = 0; i < 100; i++) { + assertTrue(set.contains("element_" + i)); + } + } + } + + @Test + void synchronizedSetIteratorTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.addAll(Arrays.asList("one", "two", "three", "four")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + int count = 0; + for (String element : set) { + assertNotNull(element); + count++; + } + assertEquals(4, count); + } + } + + @Test + void synchronizedSetWithIntegersTest() + { + Set intSet = Collections.synchronizedSet(new HashSet<>()); + intSet.add(1); + intSet.add(2); + intSet.add(3); + intSet.add(100); + intSet.add(-5); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.contains(1)); + assertTrue(set.contains(100)); + assertTrue(set.contains(-5)); + } + } + + @Test + void synchronizedSetToArrayTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.addAll(Arrays.asList("one", "two", "three")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Object[] array = set.toArray(); + assertEquals(3, array.length); + } + } + + @Test + void synchronizedSetDuplicateAttemptTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + assertTrue(stateDataField.add("element")); + assertFalse(stateDataField.add("element")); // duplicate should return false + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("element")); + } + } + + @Test + void synchronizedSetRemoveNonExistentTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.add("one"); + assertFalse(stateDataField.remove("two")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + } + } + + @Test + void synchronizedSetMaxSizeTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + + for (int i = 0; i < 1000; i++) { + stateDataField.add("item_" + i); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1000, set.size()); + assertTrue(set.contains("item_0")); + assertTrue(set.contains("item_999")); + } + } + + @Test + void synchronizedSetContainsAllTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.addAll(Arrays.asList("a", "b", "c", "d")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.containsAll(Arrays.asList("a", "b"))); + assertFalse(set.containsAll(Arrays.asList("a", "e"))); + } + } + + @Test + void synchronizedSetEmptyAfterAddAndRemoveTest() + { + stateDataField = Collections.synchronizedSet(new HashSet<>()); + stateDataField.add("temporary"); + stateDataField.remove("temporary"); + assertTrue(stateDataField.isEmpty()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/collections/UnmodifiableSetTest.java b/integration-tests/src/test/java/test/eclipse/store/various/collections/UnmodifiableSetTest.java new file mode 100644 index 000000000..3586f7a40 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/collections/UnmodifiableSetTest.java @@ -0,0 +1,391 @@ +package test.eclipse.store.various.collections; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.*; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class UnmodifiableSetTest +{ + @TempDir + Path workDir; + + private Set stateDataField; + + @Test + void unmodifiableSetBasicTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("one"); + baseSet.add("two"); + baseSet.add("three"); + + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + assertTrue(set.contains("three")); + } + } + + @Test + void unmodifiableSetEmptyTest() + { + stateDataField = Collections.unmodifiableSet(new HashSet<>()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + assertEquals(0, set.size()); + } + } + + @Test + void unmodifiableSetSingleElementTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("single"); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1, set.size()); + assertTrue(set.contains("single")); + } + } + + @Test + void unmodifiableSetWithLinkedHashSetTest() + { + LinkedHashSet baseSet = new LinkedHashSet<>(); + baseSet.add("first"); + baseSet.add("second"); + baseSet.add("third"); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("first")); + assertTrue(set.contains("second")); + assertTrue(set.contains("third")); + } + } + + @Test + void unmodifiableSetWithTreeSetTest() + { + TreeSet baseSet = new TreeSet<>(); + baseSet.add("zebra"); + baseSet.add("apple"); + baseSet.add("mango"); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("zebra")); + assertTrue(set.contains("apple")); + assertTrue(set.contains("mango")); + } + } + + @Test + void unmodifiableSetWithNullTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add(null); + baseSet.add("one"); + baseSet.add("two"); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains(null)); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + } + } + + @Test + void unmodifiableSetLargeDatasetTest() + { + HashSet baseSet = new HashSet<>(); + for (int i = 0; i < 100; i++) { + baseSet.add("element_" + i); + } + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(100, set.size()); + for (int i = 0; i < 100; i++) { + assertTrue(set.contains("element_" + i)); + } + } + } + + @Test + void unmodifiableSetIteratorTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.addAll(Arrays.asList("one", "two", "three", "four")); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + int count = 0; + for (String element : set) { + assertNotNull(element); + count++; + } + assertEquals(4, count); + } + } + + @Test + void unmodifiableSetWithIntegersTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add(1); + baseSet.add(2); + baseSet.add(3); + baseSet.add(100); + baseSet.add(-5); + Set intSet = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(intSet, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(5, set.size()); + assertTrue(set.contains(1)); + assertTrue(set.contains(100)); + assertTrue(set.contains(-5)); + } + } + + @Test + void unmodifiableSetToArrayTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.addAll(Arrays.asList("one", "two", "three")); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + Object[] array = set.toArray(); + assertEquals(3, array.length); + } + } + + @Test + void unmodifiableSetContainsTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.addAll(Arrays.asList("a", "b", "c", "d")); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.contains("a")); + assertTrue(set.contains("d")); + assertFalse(set.contains("z")); + } + } + + @Test + void unmodifiableSetContainsAllTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.addAll(Arrays.asList("a", "b", "c", "d")); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.containsAll(Arrays.asList("a", "b"))); + assertFalse(set.containsAll(Arrays.asList("a", "e"))); + } + } + + @Test + void unmodifiableSetMaxSizeTest() + { + HashSet baseSet = new HashSet<>(); + for (int i = 0; i < 1000; i++) { + baseSet.add("item_" + i); + } + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(1000, set.size()); + assertTrue(set.contains("item_0")); + assertTrue(set.contains("item_999")); + } + } + + @Test + void unmodifiableSetEqualsTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.addAll(Arrays.asList("a", "b", "c")); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + HashSet expectedSet = new HashSet<>(Arrays.asList("a", "b", "c")); + assertEquals(expectedSet, set); + } + } + + @Test + void unmodifiableSetHashCodeTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.addAll(Arrays.asList("x", "y", "z")); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertNotNull(set.hashCode()); + } + } + + @Test + void unmodifiableSetFromSetOfTest() + { + stateDataField = Collections.unmodifiableSet(Set.of("a", "b", "c")); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertTrue(set.contains("b")); + assertTrue(set.contains("c")); + } + } + + @Test + void unmodifiableSetWithDuplicatesInBaseTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("duplicate"); + baseSet.add("duplicate"); // Set naturally handles duplicates + baseSet.add("unique"); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertEquals(2, set.size()); + assertTrue(set.contains("duplicate")); + assertTrue(set.contains("unique")); + } + } + + @Test + void unmodifiableSetToStringTest() + { + HashSet baseSet = new HashSet<>(); + baseSet.add("test"); + stateDataField = Collections.unmodifiableSet(baseSet); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertNotNull(set.toString()); + assertTrue(set.toString().contains("test")); + } + } + + @Test + void unmodifiableSetIsEmptyTest() + { + HashSet baseSet = new HashSet<>(); + stateDataField = Collections.unmodifiableSet(baseSet); + assertTrue(stateDataField.isEmpty()); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(stateDataField, workDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(workDir)) { + Set set = (Set) storageManager.root(); + assertTrue(set.isEmpty()); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/deadlock/Deadlock405_414ReproTest.java b/integration-tests/src/test/java/test/eclipse/store/various/deadlock/Deadlock405_414ReproTest.java new file mode 100644 index 000000000..369cfa249 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/deadlock/Deadlock405_414ReproTest.java @@ -0,0 +1,191 @@ +package test.eclipse.store.various.deadlock; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.serializer.persistence.types.Storer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Reproducer for deadlock issues #405 and #414 in BinaryStorer.Default. + *

+ * Old documented lock order (in BinaryStorer.Default before the fix): + * 1) ObjectManager: synchronized(objectRegistry) + * 2) Storer: synchronized(this.head) + *

+ * Forbidden direction: head -> registry. + *

+ * BinaryStorer.Default.storeItem violated this in the legacy code: it held + * this.head across typeHandler.store(...), which recurses through + * apply -> register -> objectManager.ensureObjectId — and that acquires the + * registry. Concurrently another thread inside ensureObjectId holds the + * registry and then walks peer storers via synchCheckLocalRegistries, which + * acquires the peer's head. The two-thread cycle: + *

+ * Thread A: holds storer1.head, wants objectRegistry (illegal direction) + * Thread B: holds objectRegistry, wants storer1.head (legal direction) + *

+ * The fix collapses storer.head's monitor role into the registry monitor + * (objectRegistryMonitor == objectRegistry), so the two-level hierarchy + * becomes one level and the cycle cannot form. + * + *

Test strategy: + *

    + *
  • Spawn many worker threads, each owning its own lazy storer.
  • + *
  • Each worker repeatedly builds a fresh graph of unique nodes, + * calls store + commit on its own storer.
  • + *
  • Fresh instances force the ensureObjectId path that walks peer + * local registries (synchCheckLocalRegistries).
  • + *
  • A monitor thread polls {@link ThreadMXBean#findDeadlockedThreads()} + * and fails fast with a thread dump on detection.
  • + *
+ */ +public class Deadlock405_414ReproTest +{ + private static final int THREADS = 16; + private static final int ROUNDS_PER_THR = 200; + private static final int GRAPH_NODES = 200; + private static final long WALL_BUDGET_MS = 60_000L; + private static final long POLL_INTERVAL = 250L; + + @TempDir + Path tempDir; + + /** + * Linked chain — deep recursion through typeHandler.store. + */ + static final class Node + { + String name; + Node next; + + Node(final String name) + { + this.name = name; + } + } + + @Test + void parallelLazyStorers_doNotDeadlock() throws Exception + { + try (EmbeddedStorageManager storage = EmbeddedStorage.start(new ArrayList<>(), this.tempDir)) { + final CountDownLatch start = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(THREADS); + final AtomicReference failure = new AtomicReference<>(); + final Thread[] workers = new Thread[THREADS]; + + for (int t = 0; t < THREADS; t++) { + final int idx = t; + workers[t] = new Thread(() -> + { + try { + start.await(); + final Storer storer = storage.createStorer(); + for (int r = 0; r < ROUNDS_PER_THR; r++) { + final List graph = buildChain(idx, r, GRAPH_NODES); + storer.store(graph); + storer.commit(); + } + } catch (final Throwable th) { + failure.compareAndSet(null, th); + } finally { + done.countDown(); + } + }, "deadlock-worker-" + idx); + workers[t].setDaemon(true); + workers[t].start(); + } + + start.countDown(); + + final ThreadMXBean tmx = ManagementFactory.getThreadMXBean(); + final long deadline = System.currentTimeMillis() + WALL_BUDGET_MS; + + while (System.currentTimeMillis() < deadline) { + if (done.await(POLL_INTERVAL, TimeUnit.MILLISECONDS)) { + break; + } + final long[] cycle = tmx.findDeadlockedThreads(); + if (cycle != null && cycle.length > 0) { + failWithDeadlockReport(tmx, cycle); + } + } + + if (done.getCount() > 0) { + final long[] cycle = tmx.findDeadlockedThreads(); + if (cycle != null && cycle.length > 0) { + failWithDeadlockReport(tmx, cycle); + } + failWithAllThreadDump("Workers did not finish within " + + WALL_BUDGET_MS + " ms but no formal JMX deadlock cycle was detected."); + } + + if (failure.get() != null) { + throw new AssertionError("Worker threw", failure.get()); + } + } + } + + private static List buildChain(final int threadIdx, final int round, final int nodes) + { + final List list = new ArrayList<>(nodes); + Node prev = null; + for (int i = 0; i < nodes; i++) { + final Node n = new Node("t" + threadIdx + "-r" + round + "-n" + i); + if (prev != null) { + prev.next = n; + } + prev = n; + list.add(n); + } + return list; + } + + private static void failWithDeadlockReport(final ThreadMXBean tmx, final long[] ids) + { + final StringBuilder sb = new StringBuilder(); + sb.append("DEADLOCK DETECTED across ").append(ids.length).append(" threads:\n\n"); + final ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true); + for (final ThreadInfo ti : infos) { + sb.append(ti).append('\n'); + } + fail(sb.toString()); + } + + private static void failWithAllThreadDump(final String header) + { + final ThreadMXBean tmx = ManagementFactory.getThreadMXBean(); + final ThreadInfo[] infos = tmx.dumpAllThreads(true, true); + final StringBuilder sb = new StringBuilder(header).append("\n\nFull thread dump:\n\n"); + for (final ThreadInfo ti : infos) { + sb.append(ti).append('\n'); + } + fail(sb.toString()); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/BigDecimalTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/BigDecimalTest.java new file mode 100644 index 000000000..ef233ee68 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/BigDecimalTest.java @@ -0,0 +1,105 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BigDecimalTest +{ + @TempDir + Path tempDir; + + @Test + void bigDecimalStoreAndReload() + { + BigDecimal bd = new BigDecimal("123456789.987654321").setScale(9, RoundingMode.HALF_UP); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(bd, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + BigDecimal loaded = (BigDecimal) storageManager.root(); + + assertEquals(bd, loaded, "BigDecimal should be equal after storing and reloading"); + } + } + + @Test + @Disabled("https://github.com/eclipse-store/store/issues/521") + void bigDecimalDifferentScalesBehavior() + { + BigDecimal bd = new BigDecimal("1.2300").setScale(4, RoundingMode.UNNECESSARY); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(bd, tempDir)) { + } + + BigDecimal bd2 = new BigDecimal("1.23"); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(bd2, tempDir)) { + // numeric equality ignoring scale + assertEquals(bd.stripTrailingZeros(), bd2.stripTrailingZeros(), "BigDecimal numeric value should match when stripped"); + } + } + + @Test + void saveBigDecimalDataAndReload() + { + BigDecimal bd = new BigDecimal("0"); + + BigDecimalData root = new BigDecimalData(bd); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + BigDecimalData loadedRoot = new BigDecimalData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedRoot, tempDir)) { + assertEquals(bd, loadedRoot.getValue(), "BigDecimalData should be equal after storing and reloading"); + } + } + + private static class BigDecimalData + { + private BigDecimal value; + + public BigDecimalData(BigDecimal value) + { + this.value = value; + } + + public BigDecimalData() + { + } + + public BigDecimal getValue() + { + return value; + } + + public void setValue(BigDecimal value) + { + this.value = value; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/BigIntegerTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/BigIntegerTest.java new file mode 100644 index 000000000..873c9b37f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/BigIntegerTest.java @@ -0,0 +1,103 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.nio.file.Path; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BigIntegerTest +{ + @TempDir + Path tempDir; + + @Test + void bigIntegerStoreAndReload() + { + BigInteger bi = new BigInteger("123456789012345678901234567890"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(bi, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + BigInteger loaded = (BigInteger) storageManager.root(); + + assertEquals(bi, loaded, "BigInteger should be equal after storing and reloading"); + } + } + + @Test + @Disabled("https://github.com/eclipse-store/store/issues/521") + void bigIntegerVariants() + { + BigInteger first = new BigInteger("12"); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(first, tempDir)) { + } + + BigInteger loaded = new BigInteger("123"); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(first, loaded, "should be equal after storing and reloading"); + } + } + + @Test + void saveBigIntegerDataAndReload() + { + BigInteger bi = BigInteger.TEN.pow(20); + + BigIntegerData root = new BigIntegerData(bi); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + BigIntegerData loadedRoot = new BigIntegerData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedRoot, tempDir)) { + assertEquals(bi, loadedRoot.getValue(), "BigIntegerData should be equal after storing and reloading"); + } + } + + private static class BigIntegerData + { + private BigInteger value; + + public BigIntegerData(BigInteger value) + { + this.value = value; + } + + public BigIntegerData() + { + } + + public BigInteger getValue() + { + return value; + } + + public void setValue(BigInteger value) + { + this.value = value; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/CollectionsTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/CollectionsTest.java new file mode 100644 index 000000000..5a1b026dc --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/CollectionsTest.java @@ -0,0 +1,146 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.util.*; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class CollectionsTest +{ + @TempDir + Path tempDir; + + @Test + void shouldStoreAndReloadArrayList() + { + List list = new ArrayList<>(); + list.add("one"); + list.add("two"); + list.add(null); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + List loaded = (List) storageManager.root(); + + assertEquals(list, loaded); + } + } + + @Test + void shouldStoreAndReloadLinkedList() + { + LinkedList list = new LinkedList<>(); + list.add(1); + list.add(2); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + LinkedList loaded = (LinkedList) storageManager.root(); + + assertEquals(list, loaded); + } + } + + @Test + void shouldStoreAndReloadMapsAndSets() + { + Map map = new HashMap<>(); + map.put("a", 1); + map.put("b", 2); + + TreeMap tree = new TreeMap<>(); + tree.put("z", 26); + tree.put("a", 1); + + Set set = new HashSet<>(); + set.add("x"); + set.add("y"); + + ComplexCollections root = new ComplexCollections(map, tree, set); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + ComplexCollections loaded = (ComplexCollections) storageManager.root(); + + assertEquals(map, loaded.getMap()); + assertEquals(tree, loaded.getTree()); + assertEquals(set, loaded.getSet()); + } + } + + private static class ComplexCollections + { + private Map map; + private TreeMap tree; + private Set set; + + public ComplexCollections(Map map, TreeMap tree, Set set) + { + this.map = map; + this.tree = tree; + this.set = set; + } + + public ComplexCollections() + { + } + + public Map getMap() + { + return map; + } + + public TreeMap getTree() + { + return tree; + } + + public Set getSet() + { + return set; + } + + public void setMap(Map map) + { + this.map = map; + } + + public void setTree(TreeMap tree) + { + this.tree = tree; + } + + public void setSet(Set set) + { + this.set = set; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/ConcurrentCollectionsTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/ConcurrentCollectionsTest.java new file mode 100644 index 000000000..23320f06e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/ConcurrentCollectionsTest.java @@ -0,0 +1,128 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class ConcurrentCollectionsTest +{ + @TempDir + Path tempDir; + + @Test + void shouldStoreAndReloadConcurrentHashMap() + { + ConcurrentHashMap map = new ConcurrentHashMap<>(); + map.put("k1", "v1"); + map.put("k2", "v2"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(map, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + ConcurrentHashMap loaded = (ConcurrentHashMap) storageManager.root(); + + assertEquals(map, loaded); + } + } + + @Test + void shouldStoreAndReloadCopyOnWriteArrayList() + { + CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); + list.add("a"); + list.add("b"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(list, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + List loaded = (List) storageManager.root(); + + assertEquals(list, loaded); + } + } + + @Test + void shouldStoreAndReloadAtomicTypes() + { + AtomicInteger ai = new AtomicInteger(42); + AtomicReference ar = new AtomicReference<>("hello"); + + AtomicHolder root = new AtomicHolder(ai, ar); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + AtomicHolder loaded = (AtomicHolder) storageManager.root(); + + assertEquals(ai.get(), loaded.getAi().get()); + assertEquals(ar.get(), loaded.getAr().get()); + } + } + + private static class AtomicHolder + { + private AtomicInteger ai; + private AtomicReference ar; + + public AtomicHolder(AtomicInteger ai, AtomicReference ar) + { + this.ai = ai; + this.ar = ar; + } + + public AtomicHolder() + { + } + + public AtomicInteger getAi() + { + return ai; + } + + public AtomicReference getAr() + { + return ar; + } + + public void setAi(AtomicInteger ai) + { + this.ai = ai; + } + + public void setAr(AtomicReference ar) + { + this.ar = ar; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/LocaleCurrencyTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/LocaleCurrencyTest.java new file mode 100644 index 000000000..22d53fdb5 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/LocaleCurrencyTest.java @@ -0,0 +1,118 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.util.Currency; +import java.util.Locale; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LocaleCurrencyTest +{ + @TempDir + Path tempDir; + + @Test + void localeStoreAndReload() + { + Locale l = new Locale("cs", "CZ"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(l, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Locale loaded = (Locale) storageManager.root(); + + assertEquals(l, loaded, "Locale should be equal after storing and reloading"); + } + } + + @Test + void currencyStoreAndReload() + { + Currency c = Currency.getInstance("CZK"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(c, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Currency loaded = (Currency) storageManager.root(); + + assertEquals(c, loaded, "Currency should be equal after storing and reloading"); + } + } + + @Test + void saveLocaleCurrencyDataAndReload() + { + Locale l = Locale.ENGLISH; + Currency c = Currency.getInstance("USD"); + + LocaleCurrencyData root = new LocaleCurrencyData(l, c); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + LocaleCurrencyData loadedRoot = new LocaleCurrencyData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedRoot, tempDir)) { + assertEquals(l, loadedRoot.getLocale()); + assertEquals(c, loadedRoot.getCurrency()); + } + } + + private static class LocaleCurrencyData + { + private Locale locale; + private Currency currency; + + public LocaleCurrencyData(Locale locale, Currency currency) + { + this.locale = locale; + this.currency = currency; + } + + public LocaleCurrencyData() + { + } + + public Locale getLocale() + { + return locale; + } + + public Currency getCurrency() + { + return currency; + } + + public void setLocale(Locale locale) + { + this.locale = locale; + } + + public void setCurrency(Currency currency) + { + this.currency = currency; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/OptionalNetworkTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/OptionalNetworkTest.java new file mode 100644 index 000000000..1d7e88bf6 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/OptionalNetworkTest.java @@ -0,0 +1,177 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.util.Optional; +import java.util.OptionalInt; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class OptionalNetworkTest +{ + @TempDir + Path tempDir; + + @Test + void shouldStoreAndReloadOptionalPresentAndEmpty() + { + Optional present = Optional.of("value"); + Optional empty = Optional.empty(); + + OptionalHolder root = new OptionalHolder(present, empty); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + OptionalHolder loaded = (OptionalHolder) storageManager.root(); + + assertEquals(present, loaded.getPresent()); + assertEquals(empty, loaded.getEmpty()); + } + } + + @Test + void shouldStoreAndReloadOptionalInt() + { + OptionalInt oi = OptionalInt.of(7); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(oi, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + OptionalInt loaded = (OptionalInt) storageManager.root(); + + assertEquals(oi, loaded); + } + } + + @Test + void shouldStoreAndReloadUriUrlInetAddress() throws Exception + { + URI uri = new URI("http://example.com/path"); + URL url = new URL("http://example.com/path"); + InetAddress ia = InetAddress.getByName("127.0.0.1"); + + NetworkHolder root = new NetworkHolder(uri, url, ia); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + NetworkHolder loaded = (NetworkHolder) storageManager.root(); + + assertEquals(uri, loaded.getUri()); + assertEquals(url, loaded.getUrl()); + assertEquals(ia, loaded.getInetAddress()); + } + } + + private static class OptionalHolder + { + private Optional present; + private Optional empty; + + public OptionalHolder(Optional present, Optional empty) + { + this.present = present; + this.empty = empty; + } + + public OptionalHolder() + { + } + + public Optional getPresent() + { + return present; + } + + public Optional getEmpty() + { + return empty; + } + + public void setPresent(Optional present) + { + this.present = present; + } + + public void setEmpty(Optional empty) + { + this.empty = empty; + } + } + + private static class NetworkHolder + { + private URI uri; + private URL url; + private InetAddress inetAddress; + + public NetworkHolder(URI uri, URL url, InetAddress inetAddress) + { + this.uri = uri; + this.url = url; + this.inetAddress = inetAddress; + } + + public NetworkHolder() + { + } + + public URI getUri() + { + return uri; + } + + public URL getUrl() + { + return url; + } + + public InetAddress getInetAddress() + { + return inetAddress; + } + + public void setUri(URI uri) + { + this.uri = uri; + } + + public void setUrl(URL url) + { + this.url = url; + } + + public void setInetAddress(InetAddress inetAddress) + { + this.inetAddress = inetAddress; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/PathFileTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/PathFileTest.java new file mode 100644 index 000000000..aa127a426 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/PathFileTest.java @@ -0,0 +1,109 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class PathFileTest +{ + @TempDir + Path tempDir; + + @Test + void pathStoreAndReload() throws Exception + { + Path p = tempDir.resolve("subdir/test.txt"); + Files.createDirectories(p.getParent()); + Files.write(p, "hello".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(p, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Path loaded = (Path) storageManager.root(); + + assertEquals(p.toAbsolutePath().toString(), loaded.toAbsolutePath().toString(), "Path should be equal after storing and reloading"); + } + } + + @Test + void fileStoreAndReload() throws Exception + { + File f = tempDir.resolve("fileA.txt").toFile(); + Files.write(f.toPath(), "data".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(f, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + File loaded = (File) storageManager.root(); + + assertEquals(f.getAbsolutePath(), loaded.getAbsolutePath(), "File should be equal after storing and reloading"); + } + } + + @Test + void savePathFileDataAndReload() throws Exception + { + Path p = tempDir.resolve("another.txt"); + Files.write(p, "x".getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + + PathFileData root = new PathFileData(p); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + PathFileData loadedRoot = new PathFileData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedRoot, tempDir)) { + assertEquals(p.toAbsolutePath().toString(), loadedRoot.getValue().toAbsolutePath().toString(), "PathFileData should be equal after storing and reloading"); + } + } + + private static class PathFileData + { + private Path value; + + public PathFileData(Path value) + { + this.value = value; + } + + public PathFileData() + { + } + + public Path getValue() + { + return value; + } + + public void setValue(Path value) + { + this.value = value; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/SqlDateTimeTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/SqlDateTimeTest.java new file mode 100644 index 000000000..bb7742b02 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/SqlDateTimeTest.java @@ -0,0 +1,289 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class SqlDateTimeTest +{ + @TempDir + Path tempDir; + + @Test + void sqlDateStoreAndReload() + { + Date d = Date.valueOf("2020-01-02"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(d, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Date loaded = (Date) storageManager.root(); + + assertEquals(d, loaded, "Date should be equal after storing and reloading"); + } + } + + @Test + void sqlDateEpochRoundTrip() + { + Date epoch = new Date(0L); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(epoch, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Date loaded = (Date) storageManager.root(); + + assertEquals(epoch, loaded, "Epoch date should be equal after storing and reloading"); + } + } + + @Test + void sqlDateMinValueRoundTrip() + { + Date minDate = new Date(Long.MIN_VALUE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(minDate, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Date loaded = (Date) storageManager.root(); + + assertEquals(minDate, loaded, "Min date should be equal after storing and reloading"); + } + } + + @Test + void sqlDateMaxValueRoundTrip() + { + Date maxDate = new Date(Long.MAX_VALUE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(maxDate, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Date loaded = (Date) storageManager.root(); + + assertEquals(maxDate, loaded, "Max date should be equal after storing and reloading"); + } + } + + @Test + void sqlDateY2KRoundTrip() + { + Date y2k = Date.valueOf("2000-01-01"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(y2k, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Date loaded = (Date) storageManager.root(); + + assertEquals(y2k, loaded, "Y2K date should be equal after storing and reloading"); + } + } + + @Test + void sqlDateLeapDayRoundTrip() + { + Date leapDay = Date.valueOf("2020-02-29"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(leapDay, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Date loaded = (Date) storageManager.root(); + + assertEquals(leapDay, loaded, "Leap day should be equal after storing and reloading"); + } + } + + @Test + void sqlTimeRoundTrip() + { + Time time = Time.valueOf("12:34:56"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(time, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Time loaded = (Time) storageManager.root(); + + assertEquals(time, loaded, "Time should be equal after storing and reloading"); + } + } + + @Test + void sqlTimeMidnightRoundTrip() + { + Time midnight = Time.valueOf("00:00:00"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(midnight, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Time loaded = (Time) storageManager.root(); + + assertEquals(midnight, loaded, "Midnight time should be equal after storing and reloading"); + } + } + + @Test + void sqlTimeEndOfDayRoundTrip() + { + Time endOfDay = Time.valueOf("23:59:59"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(endOfDay, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Time loaded = (Time) storageManager.root(); + + assertEquals(endOfDay, loaded, "End of day time should be equal after storing and reloading"); + } + } + + @Test + void sqlTimestampRoundTrip() + { + Timestamp ts = Timestamp.valueOf("1999-12-31 23:59:59.0"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ts, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Timestamp loaded = (Timestamp) storageManager.root(); + + assertEquals(ts, loaded, "Timestamp should be equal after storing and reloading"); + } + } + + @Test + void sqlTimestampWithNanosRoundTrip() + { + Timestamp ts = Timestamp.valueOf("2020-05-15 10:30:45.123456789"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ts, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Timestamp loaded = (Timestamp) storageManager.root(); + + assertEquals(ts, loaded, "Timestamp with nanos should be equal after storing and reloading"); + assertEquals(ts.getNanos(), loaded.getNanos(), "Nanos should be preserved"); + } + } + + @Test + void sqlTimestampEpochRoundTrip() + { + Timestamp epoch = new Timestamp(0L); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(epoch, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Timestamp loaded = (Timestamp) storageManager.root(); + + assertEquals(epoch, loaded, "Epoch timestamp should be equal after storing and reloading"); + } + } + + @Test + void sqlTimestampMinValueRoundTrip() + { + Timestamp minTs = new Timestamp(Long.MIN_VALUE); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(minTs, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Timestamp loaded = (Timestamp) storageManager.root(); + + assertEquals(minTs, loaded, "Min timestamp should be equal after storing and reloading"); + } + } + + @Test + void sqlTimestampMaxNanosRoundTrip() + { + Timestamp ts = new Timestamp(System.currentTimeMillis()); + ts.setNanos(999999999); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(ts, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + Timestamp loaded = (Timestamp) storageManager.root(); + System.out.println(loaded.getNanos()); + + assertEquals(ts, loaded, "Timestamp with max nanos should be equal after storing and reloading"); + assertEquals(999999999, loaded.getNanos(), "Max nanos should be preserved"); + } + } + + @Test + void saveSqlDateTimeDataAndReload() + { + Timestamp ts = Timestamp.valueOf("1999-12-31 23:59:59.0"); + + SqlDateTimeData root = new SqlDateTimeData(ts); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SqlDateTimeData loadedRoot = new SqlDateTimeData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loadedRoot, tempDir)) { + assertEquals(ts, loadedRoot.getValue(), "Timestamp in data should be equal after storing and reloading"); + } + } + + private static class SqlDateTimeData + { + private Timestamp value; + + public SqlDateTimeData(Timestamp value) + { + this.value = value; + } + + public SqlDateTimeData() + { + } + + public Timestamp getValue() + { + return value; + } + + public void setValue(Timestamp value) + { + this.value = value; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/jdk/UUIDTest.java b/integration-tests/src/test/java/test/eclipse/store/various/jdk/UUIDTest.java new file mode 100644 index 000000000..c49de55f8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/jdk/UUIDTest.java @@ -0,0 +1,182 @@ +package test.eclipse.store.various.jdk; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class UUIDTest +{ + @TempDir + Path tempDir; + + @Test + void uuidStoreAndReload() + { + UUID id = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(id, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + UUID loaded = (UUID) storageManager.root(); + + assertEquals(id, loaded, "UUID should be equal after storing and reloading"); + } + } + + @Test + void uuidRandomRoundTrip() + { + UUID random = UUID.randomUUID(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(random, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + UUID loaded = (UUID) storageManager.root(); + + assertEquals(random, loaded, "Random UUID should be equal after storing and reloading"); + } + } + + @Test + void uuidNilRoundTrip() + { + UUID nil = new UUID(0L, 0L); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(nil, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + UUID loaded = (UUID) storageManager.root(); + + assertEquals(nil, loaded, "Nil UUID should be equal after storing and reloading"); + } + } + + @Test + void uuidVersionAndVariantRoundTrip() + { + UUID v1 = UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"); + UUID v4 = UUID.randomUUID(); + List root = Arrays.asList(v1, v4); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + List loaded = (List) storageManager.root(); + + assertEquals(1, loaded.get(0).version(), "Version 1 should be preserved after reload"); + assertEquals(2, loaded.get(0).variant(), "Variant 2 should be preserved after reload"); + assertEquals(4, loaded.get(1).version(), "Version 4 should be preserved after reload"); + assertEquals(2, loaded.get(1).variant(), "Variant 2 should be preserved after reload"); + } + } + + @Test + void uuidNameFromBytesRoundTrip() + { + UUID empty = UUID.nameUUIDFromBytes(new byte[0]); + byte[] longInput = new byte[512]; + for (int i = 0; i < longInput.length; i++) { + longInput[i] = (byte) i; + } + UUID longUuid = UUID.nameUUIDFromBytes(longInput); + + List root = Arrays.asList(empty, longUuid); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + List loaded = (List) storageManager.root(); + + assertEquals(root, loaded, "nameUUIDFromBytes should round-trip for empty and long inputs"); + } + } + + @Test + void uuidFromStringUppercaseRoundTrip() + { + UUID upper = UUID.fromString("123E4567-E89B-12D3-A456-426614174000"); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(upper, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + UUID loaded = (UUID) storageManager.root(); + + assertEquals(upper, loaded, "Uppercase UUID should parse and round-trip"); + } + } + + @Test + void uuidBoundaryBitsRoundTrip() + { + UUID maxLsb = new UUID(0L, Long.MAX_VALUE); + UUID minMsb = new UUID(Long.MIN_VALUE, 0L); + List root = Arrays.asList(maxLsb, minMsb); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") + List loaded = (List) storageManager.root(); + + assertEquals(root, loaded, "Boundary-bit UUIDs should round-trip"); + } + } + + private static class UUIDData + { + private UUID value; + + public UUIDData(UUID value) + { + this.value = value; + } + + public UUIDData() + { + } + + public UUID getValue() + { + return value; + } + + public void setValue(UUID value) + { + this.value = value; + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/ConfiguredRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/ConfiguredRestartTest.java new file mode 100644 index 000000000..5661c995d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/ConfiguredRestartTest.java @@ -0,0 +1,192 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Runs the same shutdown/start restart scenario under a matrix of storage configurations to + * confirm that a configured storage keeps working — without data loss — across repeated stop/start + * cycles on the same {@link EmbeddedStorageManager} instance, and that a fresh manager opened with + * the same configuration sees the complete graph on disk. + */ +public class ConfiguredRestartTest +{ + private static final int CYCLES = 8; + private static final int PER_CYCLE = 4; + + private static final ByteSize DATA_FILE_MIN = ByteSize.New(1, ByteUnit.KiB); + private static final ByteSize DATA_FILE_MAX = ByteSize.New(2, ByteUnit.KiB); + + static Stream configurations() + { + return Stream.of( + Arguments.of("default", (Consumer) b -> { + }), + Arguments.of("channelCount-2", cfg(b -> b.setChannelCount(2))), + Arguments.of("channelCount-4", cfg(b -> b.setChannelCount(4))), + Arguments.of("smallDataFiles", cfg(b -> b + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX) + .setDataFileMinimumUseRatio(0.9) + .setDataFileCleanupHeadFile(true))), + Arguments.of("longHousekeeping", cfg(b -> b + .setHousekeepingInterval(Duration.ofSeconds(10)) + .setHousekeepingTimeBudget(Duration.ofMillis(1)))), + Arguments.of("lowEntityCache", cfg(b -> b + .setEntityCacheThreshold(1) + .setEntityCacheTimeout(Duration.ofMillis(10)))), + // file suffixes are passed WITHOUT a leading dot — the framework inserts the '.' separator + Arguments.of("customFileNames", cfg(b -> b + .setChannelDirectoryPrefix("ch_") + .setDataFilePrefix("dat_") + .setDataFileSuffix("bin") + .setTransactionFilePrefix("tx_") + .setTransactionFileSuffix("log") + .setTypeDictionaryFileName("types.dat") + .setLockFileName("store.lock"))), + Arguments.of("combined-heavy", cfg(b -> b + .setChannelCount(4) + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX) + .setDataFileMinimumUseRatio(0.9) + .setHousekeepingInterval(Duration.ofSeconds(10)))) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("configurations") + @Timeout(60) + public void configuredStorage_survivesRestartCycles( + final String name, + final Consumer configurer, + @TempDir final Path baseDir + ) + { + final Path dir = baseDir.resolve(name); + + final List root = new ArrayList<>(); + final EmbeddedStorageManager storage = newManager(dir, configurer, root); + + for (int cycle = 0; cycle < CYCLES; cycle++) { + final List live = storage.root(); + assertEquals(cycle * PER_CYCLE, live.size(), "[" + name + "] size at start of cycle " + cycle); + + for (int j = 0; j < PER_CYCLE; j++) { + live.add("c" + cycle + "-e" + j); + } + storage.store(live); + storage.shutdown(); + + storage.start(); + + final List reloaded = storage.root(); + assertNotNull(reloaded, "[" + name + "] root after restart in cycle " + cycle); + assertEquals((cycle + 1) * PER_CYCLE, reloaded.size(), + "[" + name + "] size after restart in cycle " + cycle); + } + storage.shutdown(); + + // independent verification: a fresh manager with the SAME configuration must see the full graph + final EmbeddedStorageManager verifier = newManager(dir, configurer, new ArrayList()); + final List fromDisk = verifier.root(); + assertEquals(CYCLES * PER_CYCLE, fromDisk.size(), "[" + name + "] all entries must be persisted to disk"); + for (int cycle = 0; cycle < CYCLES; cycle++) { + for (int j = 0; j < PER_CYCLE; j++) { + final int idx = cycle * PER_CYCLE + j; + assertEquals("c" + cycle + "-e" + j, fromDisk.get(idx), "[" + name + "] entry " + idx + " on disk"); + } + } + verifier.shutdown(); + } + + @Test + @Timeout(60) + public void multiChannelBackup_survivesRestart_mirrors(@TempDir final Path dir, @TempDir final Path backup) + { + final Consumer cfg = b -> b + .setChannelCount(4) + .setBackupDirectory(backup.toAbsolutePath().toString()); + + final List root = new ArrayList<>(); + final EmbeddedStorageManager storage = newManager(dir, cfg, root); + + for (int cycle = 0; cycle < CYCLES; cycle++) { + final List live = storage.root(); + for (int j = 0; j < PER_CYCLE; j++) { + live.add("c" + cycle + "-e" + j); + } + storage.store(live); + storage.shutdown(); + storage.start(); + } + storage.shutdown(); + + // reopen the backup as primary storage (same channel count) and confirm the full graph survived + final EmbeddedStorageManager verifier = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(backup.toAbsolutePath().toString()) + .setChannelCount(4) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(new ArrayList()) + .start(); + final List fromBackup = verifier.root(); + assertEquals(CYCLES * PER_CYCLE, fromBackup.size(), + "backup must contain every entry written across restart cycles"); + assertEquals("c0-e0", fromBackup.get(0)); + assertEquals("c" + (CYCLES - 1) + "-e" + (PER_CYCLE - 1), fromBackup.get(CYCLES * PER_CYCLE - 1)); + verifier.shutdown(); + } + + private static EmbeddedStorageManager newManager( + final Path dir, + final Consumer configurer, + final List root + ) + { + final EmbeddedStorageConfigurationBuilder builder = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(dir.toAbsolutePath().toString()); + configurer.accept(builder); + return builder + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(root) + .start(); + } + + private static Consumer cfg( + final Consumer c + ) + { + return c; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/CyclicRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/CyclicRestartTest.java new file mode 100644 index 000000000..076e9fbe0 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/CyclicRestartTest.java @@ -0,0 +1,130 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Exercises many shutdown/start cycles on the same {@link EmbeddedStorageManager} instance while + * the object graph keeps growing between cycles. Guards against data loss across restarts: + * each cycle the live storage must keep every previously stored entry, and a final fresh manager + * opened on the same directory must see the complete graph (proving the data hit the disk, not + * just survived in the in-memory registry). + */ +public class CyclicRestartTest +{ + private static final int CYCLES = 5; + + @Test + public void manyCycles_growingGraph_noDataLoss(@TempDir final Path dir) + { + final List root = new ArrayList<>(); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(root, dir); + + for (int i = 0; i < CYCLES; i++) { + final List live = storage.root(); + assertEquals(i, live.size(), "entries present at start of cycle " + i); + + live.add("entry-" + i); + storage.store(live); + storage.shutdown(); + + storage.start(); + + final List reloaded = storage.root(); + assertNotNull(reloaded, "root after restart in cycle " + i); + assertEquals(i + 1, reloaded.size(), "entries after restart in cycle " + i); + assertEquals("entry-" + i, reloaded.get(i), "newest entry after restart in cycle " + i); + } + + storage.shutdown(); + + // independent verification: a fresh manager on the same directory must see the full graph + final EmbeddedStorageManager verifier = EmbeddedStorage.start(new ArrayList(), dir); + final List fromDisk = verifier.root(); + assertEquals(CYCLES, fromDisk.size(), "all entries must be persisted to disk"); + for (int i = 0; i < CYCLES; i++) { + assertEquals("entry-" + i, fromDisk.get(i), "entry " + i + " on disk"); + } + verifier.shutdown(); + } + + @Test + public void manyCycles_withBackup_mirrorsEveryCycle(@TempDir final Path dir, @TempDir final Path backup) + { + final List root = new ArrayList<>(); + + final EmbeddedStorageManager storage = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(dir.toAbsolutePath().toString()) + .setBackupDirectory(backup.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(root) + .start(); + + for (int i = 0; i < CYCLES; i++) { + final List live = storage.root(); + live.add("entry-" + i); + storage.store(live); + storage.shutdown(); + storage.start(); + } + storage.shutdown(); + + final long liveSize = totalFileSize(dir); + final long backupSize = totalFileSize(backup); + assertEquals(liveSize, backupSize, + "backup must mirror live byte-for-byte after " + CYCLES + " cycles"); + + // reopen the backup as primary and confirm the full graph survived all cycles + final EmbeddedStorageManager verifier = EmbeddedStorage.start(new ArrayList(), backup); + final List fromBackup = verifier.root(); + assertEquals(CYCLES, fromBackup.size(), "backup must contain every entry written across cycles"); + for (int i = 0; i < CYCLES; i++) { + assertEquals("entry-" + i, fromBackup.get(i), "entry " + i + " in backup"); + } + verifier.shutdown(); + } + + private static long totalFileSize(final Path directory) + { + try (final java.util.stream.Stream stream = java.nio.file.Files.walk(directory)) { + return stream + .filter(java.nio.file.Files::isRegularFile) + .mapToLong(p -> + { + try { + return java.nio.file.Files.size(p); + } catch (final java.io.IOException e) { + throw new RuntimeException(e); + } + }) + .sum(); + } catch (final java.io.IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/GarbageCollectionRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/GarbageCollectionRestartTest.java new file mode 100644 index 000000000..c01caab35 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/GarbageCollectionRestartTest.java @@ -0,0 +1,187 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.eclipse.serializer.configuration.types.ByteSize; +import org.eclipse.serializer.configuration.types.ByteUnit; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; + +/** + * Garbage collection across restarts: making objects unreachable, running a full GC, and restarting + * must keep all still-reachable data intact, and (with a backup) the backup must remain a faithful + * copy of the live storage after deletions are reclaimed across a restart. + */ +public class GarbageCollectionRestartTest +{ + private static final ByteSize DATA_FILE_MIN = ByteSize.New(1, ByteUnit.KiB); + private static final ByteSize DATA_FILE_MAX = ByteSize.New(2, ByteUnit.KiB); + + private static final int INITIAL = 200; + private static final int ADDED = 50; + + @Test + @Timeout(60) + public void fullGcAcrossRestart_liveDataIntact(@TempDir final Path dir) + { + final Consumer cfg = smallFiles(); + + final List root = new ArrayList<>(); + final EmbeddedStorageManager storage = newManager(dir, cfg, root); + + for (int i = 0; i < INITIAL; i++) { + root.add("v-" + i); + } + storage.store(root); + storage.shutdown(); + + // drop the odd entries (make them garbage), GC, restart + storage.start(); + final List live = storage.root(); + live.removeIf(s -> Integer.parseInt(s.substring(2)) % 2 == 1); + storage.store(live); + storage.issueFullGarbageCollection(); + storage.issueFullFileCheck(); + storage.shutdown(); + + storage.start(); + final List afterGc = storage.root(); + assertEquals(INITIAL / 2, afterGc.size(), "kept (even) entries must survive GC + restart"); + for (int k = 0; k < afterGc.size(); k++) { + assertEquals("v-" + (k * 2), afterGc.get(k), "kept entry " + k + " intact after GC"); + } + + // add more after GC, GC again, restart + for (int i = 0; i < ADDED; i++) { + afterGc.add("w-" + i); + } + storage.store(afterGc); + storage.issueFullGarbageCollection(); + storage.shutdown(); + + // fresh manager: full reachable set intact, nothing lost across the GC/restart interleaving + final EmbeddedStorageManager fresh = newManager(dir, cfg, new ArrayList()); + final List fromDisk = fresh.root(); + assertEquals(INITIAL / 2 + ADDED, fromDisk.size()); + for (int k = 0; k < INITIAL / 2; k++) { + assertEquals("v-" + (k * 2), fromDisk.get(k)); + } + for (int i = 0; i < ADDED; i++) { + assertEquals("w-" + i, fromDisk.get(INITIAL / 2 + i)); + } + fresh.shutdown(); + } + + @Test + @Timeout(60) + public void gcWithBackup_acrossRestart_backupMirrorsLive(@TempDir final Path dir, @TempDir final Path backup) + { + final Consumer cfg = smallFiles() + .andThen(b -> b.setBackupDirectory(backup.toAbsolutePath().toString())); + + final List root = new ArrayList<>(); + final EmbeddedStorageManager storage = newManager(dir, cfg, root); + + for (int i = 0; i < INITIAL; i++) { + root.add("v-" + i); + } + storage.store(root); + storage.shutdown(); + + // delete two thirds, GC across a restart + storage.start(); + final List live = storage.root(); + live.removeIf(s -> Integer.parseInt(s.substring(2)) % 3 != 0); + storage.store(live); + storage.issueFullGarbageCollection(); + storage.issueFullFileCheck(); + storage.shutdown(); + + storage.start(); + storage.issueFullGarbageCollection(); + storage.issueFullFileCheck(); + final int expectedKept = storage.>root().size(); + storage.shutdown(); + + final long liveSize = totalFileSize(dir); + final long backupSize = totalFileSize(backup); + + // reopen the backup as primary and verify it is a faithful copy of the surviving graph + final EmbeddedStorageManager verifier = newManager(backup, smallFiles(), new ArrayList()); + final List fromBackup = verifier.root(); + assertEquals(expectedKept, fromBackup.size(), + "backup must contain exactly the surviving entries after GC across restart"); + for (final String s : fromBackup) { + assertTrue(Integer.parseInt(s.substring(2)) % 3 == 0, "only multiples of 3 survive: " + s); + } + verifier.shutdown(); + + assertEquals(liveSize, backupSize, + "backup must mirror live byte-for-byte after GC across restart " + + "(live=" + liveSize + ", backup=" + backupSize + ")"); + } + + private static Consumer smallFiles() + { + return b -> b + .setChannelCount(1) + .setDataFileMinimumSize(DATA_FILE_MIN) + .setDataFileMaximumSize(DATA_FILE_MAX) + .setDataFileMinimumUseRatio(0.9) + .setDataFileCleanupHeadFile(true); + } + + private static EmbeddedStorageManager newManager( + final Path dir, + final Consumer configurer, + final List root + ) + { + final EmbeddedStorageConfigurationBuilder builder = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(dir.toAbsolutePath().toString()); + configurer.accept(builder); + return builder.createEmbeddedStorageFoundation().createEmbeddedStorageManager(root).start(); + } + + private static long totalFileSize(final Path directory) + { + try (final Stream stream = Files.walk(directory)) { + return stream.filter(Files::isRegularFile).mapToLong(p -> + { + try { + return Files.size(p); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }).sum(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/GigaMapRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/GigaMapRestartTest.java new file mode 100644 index 000000000..68c312c9d --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/GigaMapRestartTest.java @@ -0,0 +1,72 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; + +import org.eclipse.store.gigamap.types.GigaMap; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Restart coverage for a {@link GigaMap} held as the storage root, exercised across + * shutdown/start cycles on the same {@link EmbeddedStorageManager} instance. + */ +public class GigaMapRestartTest +{ + private static final int CYCLES = 10; + private static final int PER_CYCLE = 5; + + @Test + public void gigaMapRoot_growsAcrossCycles_noDataLoss(@TempDir final Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + final GigaMap gigaMap = storage.ensureRoot(GigaMap::New); + + for (int cycle = 0; cycle < CYCLES; cycle++) { + final GigaMap live = storage.root(); + assertEquals((long) cycle * PER_CYCLE, live.size(), "size at start of cycle " + cycle); + + for (int j = 0; j < PER_CYCLE; j++) { + live.add("c" + cycle + "-e" + j); + } + live.store(); + storage.shutdown(); + + storage.start(); + + final GigaMap reloaded = storage.root(); + assertNotNull(reloaded); + assertEquals((long) (cycle + 1) * PER_CYCLE, reloaded.size(), + "size after restart in cycle " + cycle); + } + + storage.shutdown(); + + // independent verification from a fresh manager + final EmbeddedStorageManager verifier = EmbeddedStorage.start(dir); + final GigaMap fromDisk = verifier.root(); + assertEquals((long) CYCLES * PER_CYCLE, fromDisk.size(), "all entries persisted to disk"); + assertEquals("c0-e0", fromDisk.get(0)); + assertEquals("c" + (CYCLES - 1) + "-e" + (PER_CYCLE - 1), + fromDisk.get((long) CYCLES * PER_CYCLE - 1), "last entry on disk"); + verifier.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/IdentityRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/IdentityRestartTest.java new file mode 100644 index 000000000..3e1cb1129 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/IdentityRestartTest.java @@ -0,0 +1,173 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Identity / object-id continuity across shutdown/start cycles. {@code shutdown()} truncates the + * object registry, so these tests guard against the registry reload after a restart duplicating + * shared instances, breaking enum identity, or resetting the object-id counter (which would let a + * post-restart store overwrite a pre-restart object). + */ +public class IdentityRestartTest +{ + static class Shared + { + String data; + + Shared(final String data) + { + this.data = data; + } + } + + static class Holder + { + Shared left; + Shared right; + } + + enum Color + { + RED, GREEN, BLUE + } + + static class ColorBox + { + Color color; + + ColorBox(final Color color) + { + this.color = color; + } + } + + static class Node + { + int id; + Node prev; + + Node(final int id, final Node prev) + { + this.id = id; + this.prev = prev; + } + } + + @Test + public void sharedReference_identityPreserved_afterRestart(@TempDir final Path dir) + { + final Holder holder = new Holder(); + final Shared shared = new Shared("shared-data"); + holder.left = shared; + holder.right = shared; + + final EmbeddedStorageManager storage = EmbeddedStorage.start(holder, dir); + storage.storeRoot(); + storage.shutdown(); + + storage.start(); + final Holder afterRestart = storage.root(); + assertSame(afterRestart.left, afterRestart.right, + "shared reference must stay a single instance after same-instance restart"); + storage.shutdown(); + // fresh manager: the loader must rebuild the shared reference as one instance from disk + final EmbeddedStorageManager fresh = EmbeddedStorage.start(new Holder(), dir); + final Holder fromDisk = fresh.root(); + assertNotNull(fromDisk.left); + assertSame(fromDisk.left, fromDisk.right, + "shared reference must be a single instance when loaded fresh from disk"); + assertEquals("shared-data", fromDisk.left.data); + fresh.shutdown(); + } + + @Test + public void enumIdentity_afterRestart(@TempDir final Path dir) + { + final List boxes = new ArrayList<>(); + boxes.add(new ColorBox(Color.RED)); + boxes.add(new ColorBox(Color.GREEN)); + boxes.add(new ColorBox(Color.RED)); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(boxes, dir); + storage.storeRoot(); + storage.shutdown(); + storage.start(); + storage.shutdown(); + + // fresh load must resolve enums to their canonical constants (== identity) + final EmbeddedStorageManager fresh = EmbeddedStorage.start(new ArrayList(), dir); + final List fromDisk = fresh.root(); + assertEquals(3, fromDisk.size()); + assertSame(Color.RED, fromDisk.get(0).color, "enum must resolve to the canonical constant"); + assertSame(Color.GREEN, fromDisk.get(1).color); + assertSame(Color.RED, fromDisk.get(2).color); + assertSame(fromDisk.get(0).color, fromDisk.get(2).color, "equal enums must be the same instance"); + fresh.shutdown(); + } + + @Test + public void crossCycleLinkedChain_intactAfterRestarts(@TempDir final Path dir) + { + final int chainLength = 12; + final List head = new ArrayList<>(); // head.get(0) is the tip of the chain + + final EmbeddedStorageManager storage = EmbeddedStorage.start(head, dir); + + Node prev = null; + for (int i = 0; i < chainLength; i++) { + // each cycle links a new node to the node stored in the previous cycle, then restarts + final Node node = new Node(i, prev); + if (head.isEmpty()) { + head.add(node); + } else { + head.set(0, node); + } + storage.store(head); + prev = node; + storage.shutdown(); + storage.start(); + } + storage.shutdown(); + + // fresh manager: walk the chain from disk and confirm every cross-cycle link survived, + // each node is a distinct instance, and ids are intact (no oid collision/overwrite) + final EmbeddedStorageManager fresh = EmbeddedStorage.start(new ArrayList(), dir); + final List fromDisk = fresh.root(); + Node n = fromDisk.get(0); + final List seen = new ArrayList<>(); + for (int expectedId = chainLength - 1; expectedId >= 0; expectedId--) { + assertNotNull(n, "chain truncated at expected id " + expectedId); + assertEquals(expectedId, n.id, "node id along the chain"); + for (final Node s : seen) { + assertNotSame(s, n, "each chain node must be a distinct instance"); + } + seen.add(n); + n = n.prev; + } + assertEquals(chainLength, seen.size(), "chain must contain every node stored across cycles"); + fresh.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/LazyRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/LazyRestartTest.java new file mode 100644 index 000000000..4d250f7aa --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/LazyRestartTest.java @@ -0,0 +1,125 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Verifies that {@link Lazy} references survive a shutdown/start cycle on the same + * {@link EmbeddedStorageManager} instance and can be (re)loaded from disk afterwards. + * + *

This directly exercises the restart fix in {@code EmbeddedStorageBinarySource}, which now + * resolves the current {@code StorageRequestAcceptor} via a supplier on every read: a lazy load + * triggered after a restart must go through the storage's new request acceptor, not a + * stale one captured before shutdown. + */ +public class LazyRestartTest +{ + static class Payload + { + String data; + int value; + + Payload(final String data, final int value) + { + this.data = data; + this.value = value; + } + } + + static class Holder + { + List> items = new ArrayList<>(); + } + + private static final int COUNT = 10; + + @Test + public void lazyRefs_reloadFromDisk_afterRestart(@TempDir final Path dir) + { + final Holder holder = new Holder(); + final EmbeddedStorageManager storage = EmbeddedStorage.start(holder, dir); + + for (int i = 0; i < COUNT; i++) { + holder.items.add(Lazy.Reference(new Payload("payload-" + i, i))); + } + // store the mutated list (not just the root) so the added lazy refs are persisted + storage.store(holder.items); + storage.shutdown(); + + storage.start(); + + final Holder reloaded = storage.root(); + assertNotNull(reloaded); + assertEquals(COUNT, reloaded.items.size()); + + for (int i = 0; i < COUNT; i++) { + final Lazy lazy = reloaded.items.get(i); + + // force the referent out of memory, then pull it back in — this load must be served + // by the post-restart request acceptor (the supplier self-heal path) + Lazy.clear(lazy); + assertFalse(Lazy.isLoaded(lazy), "reference must be unloaded after clear (i=" + i + ")"); + + final Payload p = lazy.get(); + assertNotNull(p, "lazy.get() must load from disk after restart (i=" + i + ")"); + assertEquals("payload-" + i, p.data); + assertEquals(i, p.value); + } + + storage.shutdown(); + } + + @Test + public void lazyRefs_mutatedAcrossCycles_persist(@TempDir final Path dir) + { + final Holder holder = new Holder(); + holder.items.add(Lazy.Reference(new Payload("v0", 0))); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(holder, dir); + storage.storeRoot(); + storage.shutdown(); + + // three cycles: each time load the lazy referent, mutate it, store, restart + for (int cycle = 1; cycle <= 3; cycle++) { + storage.start(); + final Holder reloaded = storage.root(); + final Lazy lazy = reloaded.items.get(0); + final Payload p = lazy.get(); + p.data = "v" + cycle; + p.value = cycle; + storage.store(p); + storage.shutdown(); + } + + // independent verification from a fresh manager + final EmbeddedStorageManager verifier = EmbeddedStorage.start(new Holder(), dir); + final Holder fromDisk = verifier.root(); + final Payload p = fromDisk.items.get(0).get(); + assertEquals("v3", p.data, "last mutation across cycles must be on disk"); + assertEquals(3, p.value); + verifier.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/LifecycleRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/LifecycleRestartTest.java new file mode 100644 index 000000000..4d5104fef --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/LifecycleRestartTest.java @@ -0,0 +1,99 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; + +/** + * Lifecycle / state-guard behaviour of the now-{@code synchronized} {@code start()} / {@code shutdown()}. + * Verifies {@code isRunning()} tracks each transition, shutdown reports success, repeated transitions + * are safe, and operations against a shut-down storage fail rather than silently corrupting state. + */ +public class LifecycleRestartTest +{ + @Test + @Timeout(30) + public void isRunning_tracksTransitions(@TempDir final Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + assertTrue(storage.isRunning(), "running after initial start"); + + final boolean shutdownResult = storage.shutdown(); + assertTrue(shutdownResult, "successful shutdown must report true"); + assertFalse(storage.isRunning(), "not running after shutdown"); + + storage.start(); + assertTrue(storage.isRunning(), "running again after restart"); + + assertTrue(storage.shutdown()); + assertFalse(storage.isRunning()); + } + + @Test + @Timeout(30) + public void doubleShutdown_isSafe(@TempDir final Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + assertTrue(storage.shutdown()); + // a second shutdown on an already-stopped storage must not throw or change the stopped state + assertDoesNotThrow(storage::shutdown); + assertFalse(storage.isRunning()); + } + + @Test + @Timeout(30) + public void startWhenAlreadyRunning_rejectedAndStaysRunning(@TempDir final Path dir) + { + final EmbeddedStorageManager storage = EmbeddedStorage.start(dir); + assertTrue(storage.isRunning()); + // starting an already-running storage is rejected rather than silently re-initializing + assertThrows(Exception.class, storage::start, "start on a running storage must be rejected"); + // the rejected start must leave the running storage intact + assertTrue(storage.isRunning(), "storage must stay running after a rejected start"); + assertDoesNotThrow(storage::shutdown); + } + + @Test + @Timeout(30) + public void storeAfterShutdown_fails(@TempDir final Path dir) + { + final List root = new ArrayList<>(); + root.add("alpha"); + final EmbeddedStorageManager storage = EmbeddedStorage.start(root, dir); + storage.storeRoot(); + storage.shutdown(); + + // storing against a shut-down storage must fail loudly, not silently swallow the write + assertThrows(Exception.class, () -> storage.store(root), + "store on a shut-down storage must throw"); + + // recovery: a restart must still work and preserve the pre-shutdown data + storage.start(); + final List reloaded = storage.root(); + assertEquals(1, reloaded.size()); + assertEquals("alpha", reloaded.get(0)); + storage.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/ReadOnlyRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/ReadOnlyRestartTest.java new file mode 100644 index 000000000..8f99c3830 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/ReadOnlyRestartTest.java @@ -0,0 +1,258 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.store.storage.embedded.configuration.types.EmbeddedStorageConfigurationBuilder; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageWriteControllerReadOnlyMode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Restart coverage for read-only storage. A shutdown/start cycle on a read-only + * {@link EmbeddedStorageManager} must not write anything to the storage files — the startup + * root reconciliation that would normally re-store the roots is suppressed when the storage is + * not writable (commit {@code ac151127}). Data written before opening read-only must stay + * readable across the restart. + */ +public class ReadOnlyRestartTest +{ + @Test + public void readOnlyRestart_writesNothing_dataReadable(@TempDir final Path dir) + { + // phase 1 — write data with a normal writable manager + final List seed = new ArrayList<>(); + seed.add("alpha"); + seed.add("beta"); + try (final EmbeddedStorageManager writer = EmbeddedStorage.start(seed, dir)) { + writer.storeRoot(); + } + + final Map sizesBeforeReadOnly = fileSizes(dir); + + // phase 2 — open read-only and restart on the same instance + final EmbeddedStorageFoundation foundation = EmbeddedStorage.Foundation(dir); + final StorageWriteControllerReadOnlyMode writeController = + new StorageWriteControllerReadOnlyMode(foundation.getWriteController()); + writeController.setReadOnly(true); + foundation.setWriteController(writeController); + + final EmbeddedStorageManager storage = foundation + .setRoot(new ArrayList()) + .createEmbeddedStorageManager() + .start(); + + final List loaded = storage.root(); + assertNotNull(loaded); + assertEquals(2, loaded.size(), "read-only must load the persisted root"); + assertEquals("alpha", loaded.get(0)); + assertEquals("beta", loaded.get(1)); + + storage.shutdown(); + storage.start(); + + final List reloaded = storage.root(); + assertNotNull(reloaded); + assertEquals(2, reloaded.size(), "data must still be readable after read-only restart"); + assertEquals("alpha", reloaded.get(0)); + assertEquals("beta", reloaded.get(1)); + + storage.shutdown(); + + final Map sizesAfterReadOnly = fileSizes(dir); + assertEquals(sizesBeforeReadOnly, sizesAfterReadOnly, + "read-only restart must not modify any storage file"); + } + + @Test + public void restartWithoutStore_dataStable(@TempDir final Path dir) + { + final List seed = new ArrayList<>(); + seed.add("x"); + seed.add("y"); + seed.add("z"); + + final EmbeddedStorageManager storage = EmbeddedStorage.start(seed, dir); + storage.storeRoot(); + storage.shutdown(); + + // several restart cycles with no store in between — content must remain intact + for (int i = 0; i < 5; i++) { + storage.start(); + final List reloaded = storage.root(); + assertEquals(3, reloaded.size(), "size after restart " + i); + assertEquals("x", reloaded.get(0)); + assertEquals("z", reloaded.get(2)); + storage.shutdown(); + } + } + + @Test + public void readOnly_manyRestartCycles_neverWrites(@TempDir final Path dir) + { + final List seed = new ArrayList<>(); + seed.add("a"); + seed.add("b"); + seed.add("c"); + try (final EmbeddedStorageManager writer = EmbeddedStorage.start(seed, dir)) { + writer.storeRoot(); + } + + final Map baseline = fileSizes(dir); + + final EmbeddedStorageManager storage = openReadOnly(EmbeddedStorage.Foundation(dir), new ArrayList()); + assertEquals(baseline, fileSizes(dir), "read-only start must not write"); + + for (int i = 0; i < 5; i++) { + assertEquals(3, storage.>root().size(), "content after read-only restart " + i); + storage.shutdown(); + assertEquals(baseline, fileSizes(dir), "read-only must not write on cycle " + i); + storage.start(); + } + storage.shutdown(); + assertEquals(baseline, fileSizes(dir), "read-only must not write across the whole run"); + } + + @Test + public void readOnlyRestart_lazyLoadsFromDisk(@TempDir final Path dir) + { + final List> seed = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + seed.add(Lazy.Reference("payload-" + i)); + } + try (final EmbeddedStorageManager writer = EmbeddedStorage.start(seed, dir)) { + writer.store(seed); + } + + final Map baseline = fileSizes(dir); + + final EmbeddedStorageManager storage = openReadOnly(EmbeddedStorage.Foundation(dir), new ArrayList>()); + storage.shutdown(); + storage.start(); + + final List> reloaded = storage.root(); + assertEquals(5, reloaded.size()); + for (int i = 0; i < 5; i++) { + final Lazy lazy = reloaded.get(i); + Lazy.clear(lazy); + assertEquals("payload-" + i, lazy.get(), + "read-only must lazy-load from disk after restart (i=" + i + ")"); + } + storage.shutdown(); + assertEquals(baseline, fileSizes(dir), "lazy loads on read-only must not write"); + } + + @Test + public void storeOnReadOnly_acrossRestart_rejectedAndWritesNothing(@TempDir final Path dir) + { + final List seed = new ArrayList<>(); + seed.add("alpha"); + try (final EmbeddedStorageManager writer = EmbeddedStorage.start(seed, dir)) { + writer.storeRoot(); + } + + final Map baseline = fileSizes(dir); + + final EmbeddedStorageManager storage = openReadOnly(EmbeddedStorage.Foundation(dir), new ArrayList()); + storage.shutdown(); + storage.start(); + + final List reloaded = storage.root(); + reloaded.add("beta"); + assertThrows(Exception.class, () -> storage.store(reloaded), + "store on a read-only storage must be rejected"); + + storage.shutdown(); + assertEquals(baseline, fileSizes(dir), + "a rejected store on read-only must not write anything, even across a restart"); + } + + @Test + public void readOnlyRestart_withBackup_writesNothing(@TempDir final Path dir, @TempDir final Path backup) + { + final List seed = new ArrayList<>(); + seed.add("alpha"); + seed.add("beta"); + try (final EmbeddedStorageManager writer = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(dir.toAbsolutePath().toString()) + .setBackupDirectory(backup.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation() + .createEmbeddedStorageManager(seed) + .start()) { + writer.storeRoot(); + } + + final Map baselineData = fileSizes(dir); + final Map baselineBackup = fileSizes(backup); + + final EmbeddedStorageFoundation foundation = EmbeddedStorageConfigurationBuilder.New() + .setStorageDirectory(dir.toAbsolutePath().toString()) + .setBackupDirectory(backup.toAbsolutePath().toString()) + .createEmbeddedStorageFoundation(); + final EmbeddedStorageManager storage = openReadOnly(foundation, new ArrayList()); + + storage.shutdown(); + storage.start(); + + final List reloaded = storage.root(); + assertEquals(2, reloaded.size(), "data readable on read-only restart with backup"); + assertEquals("alpha", reloaded.get(0)); + storage.shutdown(); + + assertEquals(baselineData, fileSizes(dir), "read-only restart must not write to the live directory"); + assertEquals(baselineBackup, fileSizes(backup), "read-only restart must not write to the backup directory"); + } + + private static EmbeddedStorageManager openReadOnly(final EmbeddedStorageFoundation foundation, final Object root) + { + final StorageWriteControllerReadOnlyMode writeController = + new StorageWriteControllerReadOnlyMode(foundation.getWriteController()); + writeController.setReadOnly(true); + foundation.setWriteController(writeController); + return foundation.setRoot(root).createEmbeddedStorageManager().start(); + } + + private static Map fileSizes(final Path directory) + { + final Map sizes = new HashMap<>(); + try (final Stream stream = Files.walk(directory)) { + stream.filter(Files::isRegularFile).forEach(p -> + { + try { + sizes.put(directory.relativize(p).toString(), Files.size(p)); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return sizes; + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/restart/TypeIntroductionRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/restart/TypeIntroductionRestartTest.java new file mode 100644 index 000000000..4fe311793 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/restart/TypeIntroductionRestartTest.java @@ -0,0 +1,109 @@ +package test.eclipse.store.various.restart; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Type-system continuity across restarts: a type first seen in a later cycle (after one or more + * shutdown/start cycles) must get a type handler and a non-colliding type-id, be persisted, and + * reload correctly. Guards that {@code shutdown()}'s registry truncation does not disturb the type + * dictionary / type-id assignment, which live in the connection foundation, not the object registry. + */ +public class TypeIntroductionRestartTest +{ + static class Alpha + { + String value; + + Alpha(final String value) + { + this.value = value; + } + } + + static class Beta + { + int number; + + Beta(final int number) + { + this.number = number; + } + } + + static class Gamma + { + boolean flag; + String label; + + Gamma(final boolean flag, final String label) + { + this.flag = flag; + this.label = label; + } + } + + @Test + public void newTypesIntroducedAcrossCycles_persistAndReload(@TempDir final Path dir) + { + final List root = new ArrayList<>(); + final EmbeddedStorageManager storage = EmbeddedStorage.start(root, dir); + + // cycle 0: only Alpha is known to the storage + root.add(new Alpha("a-0")); + storage.store(root); + storage.shutdown(); + storage.start(); + + // cycle 1: introduce Beta — a type the storage has never seen before this restart + ((List) storage.root()).add(new Beta(42)); + storage.store(storage.root()); + storage.shutdown(); + storage.start(); + + // cycle 2: introduce Gamma — another brand-new type after a further restart + ((List) storage.root()).add(new Gamma(true, "g-2")); + storage.store(storage.root()); + storage.shutdown(); + + // fresh manager: every type introduced at a different point in the restart history must reload + final EmbeddedStorageManager fresh = EmbeddedStorage.start(new ArrayList(), dir); + final List fromDisk = fresh.root(); + assertEquals(3, fromDisk.size(), "all entries across all type introductions must persist"); + + final Alpha alpha = assertInstanceOf(Alpha.class, fromDisk.get(0)); + assertEquals("a-0", alpha.value); + + final Beta beta = assertInstanceOf(Beta.class, fromDisk.get(1)); + assertEquals(42, beta.number); + + final Gamma gamma = assertInstanceOf(Gamma.class, fromDisk.get(2)); + assertEquals(true, gamma.flag); + assertEquals("g-2", gamma.label); + + fresh.shutdown(); + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerCloseUnderLoadTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerCloseUnderLoadTest.java new file mode 100644 index 000000000..5c72d1a1e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerCloseUnderLoadTest.java @@ -0,0 +1,162 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Disabled("only manual launch, takes to long") +public class BatchStorerCloseUnderLoadTest +{ + private static final int WRITER_THREADS = 6; + private static final int INITIAL_LIST_SIZE = 500; + private static final Duration WARMUP = Duration.ofSeconds(10); + private static final Duration WRITER_RUN_AFTER_CLOSE = Duration.ofSeconds(5); + + @TempDir + Path tempDir; + + static class DataItem + { + final int id; + String value; + int revision; + + DataItem(final int id, final String value) + { + this.id = id; + this.value = value; + this.revision = 0; + } + + void update(final String v) + { + this.value = v; + this.revision++; + } + } + + @Test + void closeUnderLoadTest() throws InterruptedException + { + final List data = new ArrayList<>(); + for (int i = 0; i < INITIAL_LIST_SIZE; i++) { + data.add(new DataItem(i, "initial-" + i)); + } + + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + final AtomicLong storeAfterClose = new AtomicLong(0); + + System.out.println("Starting close-under-load test (warmup " + WARMUP.toSeconds() + "s)..."); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(data, tempDir)) { + storage.storeRoot(); + + final BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(Duration.ofMillis(500)), + Duration.ofMillis(100) + ); + + final ExecutorService executor = Executors.newFixedThreadPool(WRITER_THREADS); + + for (int t = 0; t < WRITER_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + synchronized (data) { + data.add(new DataItem(data.size(), "w" + idx + "-" + iteration)); + storer.store(data); + } + iteration++; + Thread.yield(); + } catch (final Exception e) { + // After close() any exception from store() is acceptable. + storeAfterClose.incrementAndGet(); + } + } + }); + } + + startLatch.countDown(); + Thread.sleep(WARMUP.toMillis()); + + System.out.println("Calling close() while writers are running..."); + final long closedAt = System.currentTimeMillis(); + + // close() must not deadlock — run it in a separate thread with a timeout. + final Thread closingThread = new Thread(storer::close); + closingThread.start(); + closingThread.join(30_000); + + System.out.println("close() returned after " + (System.currentTimeMillis() - closedAt) + "ms"); + assertFalse(closingThread.isAlive(), "close() deadlocked - did not return within 30s"); + + // Keep writers running to exercise post-close store() calls. + Thread.sleep(WRITER_RUN_AFTER_CLOSE.toMillis()); + + stop.set(true); + executor.shutdown(); + executor.awaitTermination(15, TimeUnit.SECONDS); + + System.out.println("store() calls after close(): " + storeAfterClose.get()); + System.out.println("In-memory size: " + data.size()); + } + + System.out.println("Reloading storage from disk..."); + + try (EmbeddedStorageManager reloadStorage = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") final List reloaded = (List) reloadStorage.root(); + + assertFalse(reloaded.isEmpty(), "Reloaded list must not be empty"); + + int nulls = 0; + for (int i = 0; i < reloaded.size(); i++) { + if (reloaded.get(i) == null) nulls++; + } + System.out.println("Reloaded size: " + reloaded.size() + " | null items: " + nulls); + assertTrue(nulls == 0, "Found " + nulls + " null items after reload"); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerConcurrentFlushTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerConcurrentFlushTest.java new file mode 100644 index 000000000..0b1b5ac6e --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerConcurrentFlushTest.java @@ -0,0 +1,191 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Scenario 2: concurrent flush() from multiple threads. + *

+ * Writers feed data into a single BatchStorer while a separate pool of flush + * threads hammers storer.flush() in a tight loop. The goal is to expose race + * conditions between data accumulation and flushing inside BatchStorer. + *

+ * After the stress phase the storage is reloaded and basic integrity is + * verified. + */ +@Disabled("only manual launch, takes to long") +public class BatchStorerConcurrentFlushTest +{ + private static final int WRITER_THREADS = 4; + private static final int FLUSH_THREADS = 4; + private static final int INITIAL_LIST_SIZE = 500; + private static final Duration TEST_DURATION = Duration.ofMinutes(5); + + @TempDir + Path tempDir; + + static class DataItem + { + final int id; + String value; + int revision; + + DataItem(final int id, final String value) + { + this.id = id; + this.value = value; + this.revision = 0; + } + + void update(final String v) + { + this.value = v; + this.revision++; + } + } + + @Test + void concurrentFlushStressTest() throws InterruptedException + { + final List data = new ArrayList<>(); + for (int i = 0; i < INITIAL_LIST_SIZE; i++) { + data.add(new DataItem(i, "initial-" + i)); + } + + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + final AtomicLong flushCalls = new AtomicLong(0); + final AtomicLong storeCalls = new AtomicLong(0); + final AtomicReference firstError = new AtomicReference<>(); + + System.out.println("Starting concurrent flush stress test (" + TEST_DURATION.toMinutes() + " min)..."); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(data, tempDir)) { + storage.storeRoot(); + + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(10_000, Duration.ofSeconds(2)), + Duration.ofMillis(50) + )) { + final ExecutorService executor = + Executors.newFixedThreadPool(WRITER_THREADS + FLUSH_THREADS); + + for (int t = 0; t < WRITER_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + synchronized (data) { + data.add(new DataItem(data.size(), "w" + idx + "-" + iteration)); + storer.store(data); + storeCalls.incrementAndGet(); + + final int target = (idx * 17 + iteration * 5) % data.size(); + final DataItem item = data.get(target); + item.update("w" + idx + "-upd-" + iteration); + storer.store(item); + storeCalls.incrementAndGet(); + } + iteration++; + Thread.yield(); + } catch (final Exception e) { + firstError.compareAndSet(null, e); + e.printStackTrace(); + } + } + }); + } + + for (int t = 0; t < FLUSH_THREADS; t++) { + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + while (!stop.get()) { + try { + storer.flush(); + flushCalls.incrementAndGet(); + Thread.yield(); + } catch (final Exception e) { + firstError.compareAndSet(null, e); + e.printStackTrace(); + } + } + }); + } + + startLatch.countDown(); + Thread.sleep(TEST_DURATION.toMillis()); + stop.set(true); + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + System.out.println("store() calls: " + storeCalls.get()); + System.out.println("flush() calls: " + flushCalls.get()); + System.out.println("In-memory size: " + data.size()); + } + } + + assertNull(firstError.get(), "Unexpected exception during stress: " + firstError.get()); + + try (EmbeddedStorageManager reloadStorage = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") final List reloaded = (List) reloadStorage.root(); + + assertFalse(reloaded.isEmpty(), "Reloaded list must not be empty"); + + int nulls = 0; + for (int i = 0; i < reloaded.size(); i++) { + if (reloaded.get(i) == null) nulls++; + } + System.out.println("Reloaded size: " + reloaded.size() + " | null items: " + nulls); + assertTrue(nulls == 0, "Found " + nulls + " null items after reload"); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerConcurrentTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerConcurrentTest.java new file mode 100644 index 000000000..f6b5cb4a7 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerConcurrentTest.java @@ -0,0 +1,225 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Disabled("only manual launch, takes to long") +public class BatchStorerConcurrentTest +{ + private static final Duration TEST_DURATION = Duration.ofMinutes(5); + private static final int WRITER_THREADS = 8; + private static final int INITIAL_LIST_SIZE = 1_000; + + @TempDir + Path tempDir; + + static class DataItem + { + final int id; + String value; + int revision; + + DataItem(final int id, final String value) + { + this.id = id; + this.value = value; + this.revision = 0; + } + + void update(final String newValue) + { + this.value = newValue; + this.revision++; + } + + @Override + public String toString() + { + return "DataItem{id=" + id + ", revision=" + revision + ", value='" + value + "'}"; + } + } + + @Test + void concurrentWritersStressTest() throws InterruptedException + { + System.out.println(tempDir.toString()); + final List data = new ArrayList<>(); + for (int i = 0; i < INITIAL_LIST_SIZE; i++) { + data.add(new DataItem(i, "initial-" + i)); + } + + final AtomicInteger[] addedByThread = new AtomicInteger[WRITER_THREADS]; + for (int i = 0; i < WRITER_THREADS; i++) { + addedByThread[i] = new AtomicInteger(0); + } + + final AtomicLong totalAdded = new AtomicLong(0); + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + + System.out.println("Starting BatchStorer concurrent stress test (" + TEST_DURATION.toMinutes() + " min)..."); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(data, tempDir)) { + + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(4000), + Duration.ofMillis(100) + )) { + final ExecutorService executor = Executors.newFixedThreadPool(WRITER_THREADS); + + for (int threadIdx = 0; threadIdx < WRITER_THREADS; threadIdx++) { + final int idx = threadIdx; + final AtomicInteger myAdded = addedByThread[idx]; + + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int localAdds = 0; + int iteration = 0; + + while (!stop.get()) { + try { + synchronized (data) { + data.add(new DataItem(data.size(), "thread-" + idx + "-iter-" + iteration)); + storer.store(data); + } + localAdds++; + myAdded.incrementAndGet(); + totalAdded.incrementAndGet(); + + synchronized (data) { + if (!data.isEmpty()) { + final int target = (idx * 31 + iteration * 7) % data.size(); + final DataItem item = data.get(target); + item.update("updated-by-thread-" + idx + "-at-iter-" + iteration); + storer.store(item); + storer.store(data); + } + } + + iteration++; + Thread.yield(); + } catch (final Exception e) { + System.err.println("Writer thread " + idx + " caught exception: " + e); + e.printStackTrace(); + } + } + + System.out.println("Writer thread " + idx + " finished after " + localAdds + " adds."); + }); + } + + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + + while (!stop.get()) { + try { + synchronized (data) { + data.add(new DataItem(data.size(), "direct-" + iteration)); + storage.store(data); + + if (data.size() > 1) { + final int target = (iteration * 13) % data.size(); + final DataItem item = data.get(target); + item.update("direct-store-iter-" + iteration); + storage.store(item); + } + } + iteration++; + Thread.yield(); + } catch (final Exception e) { + System.err.println("Direct-store thread caught exception: " + e); + e.printStackTrace(); + } + } + + System.out.println("Direct-store thread finished after " + iteration + " iterations."); + }); + + startLatch.countDown(); + Thread.sleep(TEST_DURATION.toMillis()); + stop.set(true); + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + System.out.println("Stress phase complete. Total items added: " + totalAdded.get()); + System.out.println("Final list size (in-memory): " + data.size()); + } + } + + System.out.println("Reloading storage from disk..."); + + try (EmbeddedStorageManager reloadStorage = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") final List reloaded = (List) reloadStorage.root(); + + assertFalse(reloaded.isEmpty(), "Reloaded list must not be empty"); + + int errors = 0; + for (int i = 0; i < reloaded.size(); i++) { + if (reloaded.get(i) == null) { + System.err.println("Null item at index " + i); + errors++; + } + } + + assertTrue(errors == 0, "Found " + errors + " null/corrupt items in reloaded list"); + + long totalExpectedAdds = INITIAL_LIST_SIZE; + for (int i = 0; i < WRITER_THREADS; i++) { + final int added = addedByThread[i].get(); + totalExpectedAdds += added; + System.out.println(" Thread " + i + " added " + added + " items"); + } + System.out.println("Expected max list size: " + totalExpectedAdds); + System.out.println("Actual reloaded size: " + reloaded.size()); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerHighThroughputTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerHighThroughputTest.java new file mode 100644 index 000000000..99158f8a4 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerHighThroughputTest.java @@ -0,0 +1,231 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Stress test for BatchStorer internal synchronization. + *

+ * Multiple writer threads each own a private partition of the shared list and + * store DIFFERENT objects into a single BatchStorer instance concurrently. + * User data access is properly synchronized — the pressure is purely on the + * BatchStorer internals (pending-map, flush pipeline, storage channel locking). + *

+ * Concurrent flush threads call storer.flush() in a tight loop to maximise + * overlap between serialization and store() enqueuing. + *

+ * After the run the storage is reloaded and the exact item count is verified. + */ +@Disabled("only manual launch, takes to long") +public class BatchStorerHighThroughputTest +{ + private static final Duration TEST_DURATION = Duration.ofMinutes(5); + private static final int WRITER_THREADS = 8; + private static final int FLUSH_THREADS = 4; + private static final int ITEMS_PER_BATCH = 50; + + @TempDir + Path tempDir; + + static class DataItem + { + final int id; + String value; + int revision; + final byte[] payload; + + DataItem(final int id, final String value) + { + this.id = id; + this.value = value; + this.revision = 0; + this.payload = new byte[(id % 64) + 16]; + } + + void update(final String v) + { + this.value = v; + this.revision++; + } + } + + static class Root + { + final List items = new ArrayList<>(); + } + + @Test + void highThroughputTest() throws InterruptedException + { + final Root root = new Root(); + for (int i = 0; i < 200; i++) { + root.items.add(new DataItem(i, "seed-" + i)); + } + + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + final AtomicReference firstError = new AtomicReference<>(); + final AtomicLong storeCalls = new AtomicLong(0); + final AtomicLong flushCalls = new AtomicLong(0); + final AtomicLong addedTotal = new AtomicLong(0); + + System.out.println("Starting high-throughput BatchStorer test (" + TEST_DURATION.toMinutes() + " min)..."); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(root, tempDir)) { + storage.storeRoot(); + storage.store(root.items); + + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(500, Duration.ofMillis(400)), + Duration.ofMillis(50) + )) { + final ExecutorService executor = + Executors.newFixedThreadPool(WRITER_THREADS + FLUSH_THREADS); + + // Writer threads: each adds items in batches and updates existing items. + for (int t = 0; t < WRITER_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + // Add a batch of new items. + synchronized (root.items) { + for (int b = 0; b < ITEMS_PER_BATCH; b++) { + final DataItem item = new DataItem( + root.items.size(), + "w" + idx + "-i" + iteration + "-b" + b + ); + root.items.add(item); + storer.store(item); + } + storer.store(root.items); + storeCalls.addAndGet(ITEMS_PER_BATCH + 1); + addedTotal.addAndGet(ITEMS_PER_BATCH); + } + + // Update existing items from own "zone". + synchronized (root.items) { + for (int u = 0; u < 10; u++) { + final int target = (idx * 31 + iteration * 7 + u * 13) % root.items.size(); + final DataItem item = root.items.get(target); + item.update("w" + idx + "-upd-" + iteration); + storer.store(item); + storeCalls.incrementAndGet(); + } + } + + iteration++; + Thread.yield(); + } catch (final Exception e) { + if (firstError.compareAndSet(null, e)) e.printStackTrace(); + } + } + }); + } + + // Flush threads: hammer flush() to create maximum contention. + for (int t = 0; t < FLUSH_THREADS; t++) { + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + while (!stop.get()) { + try { + storer.flush(); + flushCalls.incrementAndGet(); + } catch (final Exception e) { + if (firstError.compareAndSet(null, e)) e.printStackTrace(); + } + } + }); + } + + startLatch.countDown(); + Thread.sleep(TEST_DURATION.toMillis()); + stop.set(true); + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + System.out.println("store() calls: " + storeCalls.get() + + " | flush() calls: " + flushCalls.get() + + " | items added: " + addedTotal.get()); + System.out.println("In-memory size: " + root.items.size()); + System.out.println("hasPendingData before close: " + storer.hasPendingData()); + } + // BatchStorer closed — final flush happened. + System.out.println("First error during run: " + firstError.get()); + } + + final int expectedSize; + synchronized (root.items) { + expectedSize = root.items.size(); + } + + System.out.println("Reloading storage from disk..."); + try (EmbeddedStorageManager reloadStorage = EmbeddedStorage.start(tempDir)) { + final Root reloaded = (Root) reloadStorage.root(); + assertNotNull(reloaded, "Root must not be null"); + assertFalse(reloaded.items.isEmpty(), "Reloaded items list must not be empty"); + + int nulls = 0; + for (int i = 0; i < reloaded.items.size(); i++) { + if (reloaded.items.get(i) == null) nulls++; + } + + System.out.println("Reloaded items: " + reloaded.items.size() + + " | expected: " + expectedSize + + " | null items: " + nulls); + + assertTrue(nulls == 0, "Found " + nulls + " null items after reload"); + assertEquals(expectedSize, reloaded.items.size(), + "Reloaded size does not match in-memory size"); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerMutationDuringFlushTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerMutationDuringFlushTest.java new file mode 100644 index 000000000..54084181b --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerMutationDuringFlushTest.java @@ -0,0 +1,190 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Writers mutate object fields WITHOUT synchronization while the BatchStorer + * background daemon serializes the same objects mid-flight. + *

+ * The goal is to detect torn reads inside the serializer (field A from + * state N, field B from state N+1) or internal crashes such as + * ConcurrentModificationException, NPE, or corrupted storage. + *

+ * After reload every persisted item must be non-null and deserializable. + */ +@Disabled("only manual launch, takes to long") +public class BatchStorerMutationDuringFlushTest +{ + private static final Duration TEST_DURATION = Duration.ofMinutes(3); + private static final int MUTATOR_THREADS = 6; + private static final int STORER_THREADS = 2; + private static final int LIST_SIZE = 500; + + @TempDir + Path tempDir; + + static class MutableItem + { + int counter; + String label; + byte[] payload; + + MutableItem(final int counter, final String label) + { + this.counter = counter; + this.label = label; + this.payload = new byte[64]; + } + } + + static class Root + { + final List items = new ArrayList<>(); + } + + @Test + void mutationDuringFlushTest() throws InterruptedException + { + final Root root = new Root(); + for (int i = 0; i < LIST_SIZE; i++) { + root.items.add(new MutableItem(i, "init-" + i)); + } + + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + final AtomicReference firstError = new AtomicReference<>(); + final AtomicLong mutations = new AtomicLong(0); + final AtomicLong stores = new AtomicLong(0); + + System.out.println("Starting mutation-during-flush test (" + TEST_DURATION.toMinutes() + " min)..."); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(root, tempDir)) { + storage.storeRoot(); + + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(200, Duration.ofMillis(300)), + Duration.ofMillis(50) + )) { + final ExecutorService executor = + Executors.newFixedThreadPool(MUTATOR_THREADS + STORER_THREADS); + + // Mutator threads: change object fields without any synchronization. + for (int t = 0; t < MUTATOR_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + final int target = (idx * 37 + iteration * 11) % LIST_SIZE; + final MutableItem item = root.items.get(target); + // Unsynchronized mutation — intentional. + item.counter = iteration; + item.label = "m" + idx + "-" + iteration; + item.payload = new byte[(iteration % 128) + 1]; + mutations.incrementAndGet(); + iteration++; + } catch (final Exception e) { + if (firstError.compareAndSet(null, e)) e.printStackTrace(); + } + } + }); + } + + // Storer threads: call store() on items and the list without sync. + for (int t = 0; t < STORER_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + final int target = (idx * 23 + iteration * 3) % LIST_SIZE; + storer.store(root.items.get(target)); + storer.store(root.items); + stores.incrementAndGet(); + iteration++; + Thread.yield(); + } catch (final Exception e) { + if (firstError.compareAndSet(null, e)) e.printStackTrace(); + } + } + }); + } + + startLatch.countDown(); + Thread.sleep(TEST_DURATION.toMillis()); + stop.set(true); + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + System.out.println("Mutations: " + mutations.get() + " | store() calls: " + stores.get()); + } + } + + System.out.println("First error during run: " + firstError.get()); + + System.out.println("Reloading storage from disk..."); + try (EmbeddedStorageManager reloadStorage = EmbeddedStorage.start(tempDir)) { + final Root reloaded = (Root) reloadStorage.root(); + assertNotNull(reloaded, "Root must not be null"); + assertFalse(reloaded.items.isEmpty(), "Reloaded items list must not be empty"); + + int nulls = 0; + for (int i = 0; i < reloaded.items.size(); i++) { + if (reloaded.items.get(i) == null) nulls++; + } + System.out.println("Reloaded items: " + reloaded.items.size() + " | null items: " + nulls); + assertFalse(nulls > 0, "Found " + nulls + " null items after reload"); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerRepeatedRestartTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerRepeatedRestartTest.java new file mode 100644 index 000000000..603c87e0f --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerRepeatedRestartTest.java @@ -0,0 +1,177 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Disabled("only manual launch, takes to long") +public class BatchStorerRepeatedRestartTest +{ + private static final int CYCLES = 20; + private static final Duration CYCLE_DURATION = Duration.ofSeconds(15); + private static final int WRITER_THREADS = 4; + private static final int INITIAL_LIST_SIZE = 200; + + @TempDir + Path tempDir; + + static class DataItem + { + final int id; + String value; + int revision; + + DataItem(final int id, final String value) + { + this.id = id; + this.value = value; + this.revision = 0; + } + + void update(final String v) + { + this.value = v; + this.revision++; + } + } + + @Test + void repeatedRestartTest() throws InterruptedException + { + // Populate initial data in the first cycle. + final List firstData = new ArrayList<>(); + for (int i = 0; i < INITIAL_LIST_SIZE; i++) { + firstData.add(new DataItem(i, "initial-" + i)); + } + + try (EmbeddedStorageManager bootstrap = EmbeddedStorage.start(firstData, tempDir)) { + } + + int previousSize = INITIAL_LIST_SIZE; + + for (int cycle = 0; cycle < CYCLES; cycle++) { + System.out.println("=== Cycle " + (cycle + 1) + "/" + CYCLES + " ==="); + + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + final AtomicReference firstError = new AtomicReference<>(); + final int cycleLabel = cycle; + final AtomicInteger inMemorySize = new AtomicInteger(0); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") final List data = (List) storage.root(); + + System.out.println(" Loaded size at cycle start: " + data.size()); + assertTrue(data.size() >= previousSize, + "Cycle " + cycleLabel + ": size " + data.size() + " is less than previous " + previousSize); + + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(Duration.ofMillis(500)), + Duration.ofMillis(100) + )) { + final ExecutorService executor = Executors.newFixedThreadPool(WRITER_THREADS); + + for (int t = 0; t < WRITER_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + synchronized (data) { + data.add(new DataItem(data.size(), "c" + cycleLabel + "-w" + idx + "-" + iteration)); + storer.store(data); + + final int target = (idx * 31 + iteration * 7) % data.size(); + final DataItem item = data.get(target); + item.update("c" + cycleLabel + "-upd-" + idx + "-" + iteration); + storer.store(item); + } + iteration++; + Thread.yield(); + } catch (final Exception e) { + firstError.compareAndSet(null, e); + e.printStackTrace(); + } + } + }); + } + + startLatch.countDown(); + Thread.sleep(CYCLE_DURATION.toMillis()); + stop.set(true); + executor.shutdown(); + executor.awaitTermination(15, TimeUnit.SECONDS); + + inMemorySize.set(data.size()); + System.out.println(" In-memory size at cycle end: " + inMemorySize.get()); + previousSize = inMemorySize.get(); + } + } + + assertNull(firstError.get(), "Cycle " + cycle + " error: " + firstError.get()); + + // Verify the data after close — reloaded size must exactly match in-memory size. + try (EmbeddedStorageManager verify = EmbeddedStorage.start(tempDir)) { + @SuppressWarnings("unchecked") final List reloaded = (List) verify.root(); + + assertFalse(reloaded.isEmpty(), "Cycle " + cycle + ": reloaded list is empty"); + + int nulls = 0; + for (int i = 0; i < reloaded.size(); i++) { + if (reloaded.get(i) == null) nulls++; + } + assertTrue(nulls == 0, "Cycle " + cycle + ": found " + nulls + " null items"); + + System.out.println(" Reloaded size after cycle: " + reloaded.size() + + " | expected: " + inMemorySize.get() + " | nulls: " + nulls); + assertEquals(inMemorySize.get(), reloaded.size(), + "Cycle " + cycle + ": reloaded size does not match in-memory size"); + + previousSize = reloaded.size(); + } + } + + System.out.println("All " + CYCLES + " cycles completed successfully."); + } +} + + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerSameObjectRaceTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerSameObjectRaceTest.java new file mode 100644 index 000000000..8d53a66c8 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerSameObjectRaceTest.java @@ -0,0 +1,190 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + + +/** + * Integration stress test that intentionally lacks synchronization to provoke + * race conditions when storing the same object concurrently. + *

+ * Purpose: + * - Produce persistent storage containing inconsistent / "zombie" elements + * useful for debugging and reproducing concurrency bugs in the storage layer. + * - Serve as a manual, long-running reproduction scenario rather than a unit test. + *

+ * Notes: + * - The test is disabled by default and intended for manual execution only. + * - Mutates the same object from multiple threads and calls `store` / `flush` + * concurrently without coordination. This is deliberate; do not "fix" application + * logic here — the repository uses this to expose issues in the underlying library. + */ + +@Disabled("only manual launch, takes to long, produce zombies") +public class BatchStorerSameObjectRaceTest +{ + private static final Duration TEST_DURATION = Duration.ofMinutes(3); + private static final int STORE_THREADS = 8; + private static final int FLUSH_THREADS = 2; + + @TempDir + Path tempDir; + + static class SharedState + { + int version; + String label; + List tags; + + SharedState() + { + this.version = 0; + this.label = "v0"; + this.tags = new ArrayList<>(); + this.tags.add("initial"); + } + } + + static class Root + { + final SharedState shared = new SharedState(); + } + + @Test + void sameObjectRaceTest() throws InterruptedException + { + final Root root = new Root(); + + final AtomicBoolean stop = new AtomicBoolean(false); + final CountDownLatch startLatch = new CountDownLatch(1); + final AtomicReference firstError = new AtomicReference<>(); + final AtomicLong storeCalls = new AtomicLong(0); + final AtomicLong flushCalls = new AtomicLong(0); + + System.out.println("Starting same-object race test (" + TEST_DURATION.toMinutes() + " min)..."); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(root, tempDir)) { + storage.storeRoot(); + + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(50, Duration.ofMillis(200)), + Duration.ofMillis(30) + )) { + final ExecutorService executor = + Executors.newFixedThreadPool(STORE_THREADS + FLUSH_THREADS); + + for (int t = 0; t < STORE_THREADS; t++) { + final int idx = t; + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + int iteration = 0; + while (!stop.get()) { + try { + root.shared.version = iteration; + root.shared.label = "t" + idx + "-v" + iteration; + root.shared.tags = new ArrayList<>(); + root.shared.tags.add("t" + idx); + root.shared.tags.add("i" + iteration); + + storer.store(root.shared); + storer.store(root); + storeCalls.incrementAndGet(); + iteration++; + } catch (final Exception e) { + if (firstError.compareAndSet(null, e)) e.printStackTrace(); + } + } + }); + } + + for (int t = 0; t < FLUSH_THREADS; t++) { + executor.submit(() -> + { + try { + startLatch.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + while (!stop.get()) { + try { + storer.flush(); + flushCalls.incrementAndGet(); + } catch (final Exception e) { + if (firstError.compareAndSet(null, e)) e.printStackTrace(); + } + } + }); + } + + startLatch.countDown(); + Thread.sleep(TEST_DURATION.toMillis()); + stop.set(true); + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + System.out.println("store() calls: " + storeCalls.get() + + " | flush() calls: " + flushCalls.get()); + System.out.println("hasPendingData after stop: " + storer.hasPendingData()); + } + + System.out.println("First error during run: " + firstError.get()); + } + + System.out.println("Reloading storage from disk..."); + try (EmbeddedStorageManager reloadStorage = EmbeddedStorage.start(tempDir)) { + final Root reloaded = (Root) reloadStorage.root(); + assertNotNull(reloaded, "Root must not be null"); + assertNotNull(reloaded.shared, "SharedState must not be null"); + assertNotNull(reloaded.shared.label, "label must not be null"); + assertNotNull(reloaded.shared.tags, "tags must not be null"); + assertFalse(reloaded.shared.tags.isEmpty(), "tags must not be empty"); + + System.out.println("Reloaded: version=" + reloaded.shared.version + + " label=" + reloaded.shared.label + + " tags=" + reloaded.shared.tags); + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerTest.java b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerTest.java new file mode 100644 index 000000000..97c995a2c --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/storer/BatchStorerTest.java @@ -0,0 +1,66 @@ +package test.eclipse.store.various.storer; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; + +import org.eclipse.serializer.persistence.types.BatchStorer; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BatchStorerTest +{ + @TempDir + Path tempDir; + + + @Test + void bathStorerBasicTest() throws InterruptedException + { + + ArrayList data = new ArrayList<>(); + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(data, tempDir)) { + try (BatchStorer storer = storage.createBatchStorer( + BatchStorer.Controller(Duration.ofMillis(500)), + Duration.ofMillis(200) + )) { + for (int i = 0; i < 1_000; i++) { + String s = "String " + i; + data.add(s); + storer.store(data); +// XThreads.sleep(10); + } + //storer.commit(); + } + } + + try (EmbeddedStorageManager storage = EmbeddedStorage.start(tempDir)) { + ArrayList list = storage.root(); + + System.out.println("List size: " + list.size()); + System.out.println("Last Element: " + list.get(list.size() - 1)); + assertEquals(1000, list.size()); + assertEquals("String 999", list.get(list.size() - 1)); + } + + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/various/text/ChoiceFormatTest.java b/integration-tests/src/test/java/test/eclipse/store/various/text/ChoiceFormatTest.java new file mode 100644 index 000000000..0e68a53bf --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/text/ChoiceFormatTest.java @@ -0,0 +1,260 @@ +package test.eclipse.store.various.text; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.text.ChoiceFormat; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class ChoiceFormatTest +{ + @TempDir + Path tempDir; + + @Test + void choiceFormatStoreAndReload() + { + double[] limits = {0, 1, 5}; + String[] formats = {"zero", "one", "many"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(cf, tempDir)) { + } + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(tempDir)) { + ChoiceFormat loaded = (ChoiceFormat) storageManager.root(); + assertEquals("many", loaded.format(10)); + } + } + + @Test + void choiceFormatAsFieldInDataClass() + { + double[] limits = {0, 1, 5}; + String[] formats = {"zero", "one", "many"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("one", loaded.getChoice().format(1)); + } + } + + @Test + void choiceFormatWithNegativeNumbers() + { + double[] limits = {-10, -5, 0, 5, 10}; + String[] formats = {"very negative", "negative", "zero", "positive", "very positive"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("very negative", loaded.getChoice().format(-10)); + assertEquals("negative", loaded.getChoice().format(-5)); + assertEquals("positive", loaded.getChoice().format(7)); + assertEquals("very positive", loaded.getChoice().format(10)); + } + } + + @Test + void choiceFormatWithBoundaryValues() + { + double[] limits = {0, 1, 5}; + String[] formats = {"zero", "one", "many"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("zero", loaded.getChoice().format(0)); + assertEquals("one", loaded.getChoice().format(1)); + assertEquals("many", loaded.getChoice().format(5)); + assertEquals("many", loaded.getChoice().format(100)); + } + } + + @Test + void choiceFormatWithDoubleValues() + { + double[] limits = {0.0, 0.5, 1.0}; + String[] formats = {"low", "medium", "high"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("low", loaded.getChoice().format(0.3)); + assertEquals("medium", loaded.getChoice().format(0.7)); + assertEquals("high", loaded.getChoice().format(1.5)); + } + } + + @Test + void choiceFormatWithLargeArray() + { + double[] limits = new double[10]; + String[] formats = new String[10]; + for (int i = 0; i < 10; i++) { + limits[i] = i * 10.0; + formats[i] = "range" + i; + } + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("range0", loaded.getChoice().format(5)); + assertEquals("range5", loaded.getChoice().format(55)); + assertEquals("range9", loaded.getChoice().format(95)); + } + } + + @Test + void choiceFormatWithSingleElement() + { + double[] limits = {0}; + String[] formats = {"all"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("all", loaded.getChoice().format(-100)); + assertEquals("all", loaded.getChoice().format(0)); + assertEquals("all", loaded.getChoice().format(100)); + } + } + + @Test + void choiceFormatWithInfinity() + { + double[] limits = {Double.NEGATIVE_INFINITY, 0, 1}; + String[] formats = {"negative", "zero", "positive"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + assertEquals("negative", cf.format(-1000)); + assertEquals("zero", cf.format(0)); + assertEquals("positive", cf.format(1000)); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("negative", loaded.getChoice().format(-1000)); + assertEquals("zero", loaded.getChoice().format(0)); + assertEquals("positive", loaded.getChoice().format(1000)); + } + } + + @Test + void choiceFormatWithSpecialStrings() + { + double[] limits = {0, 1, 2}; + String[] formats = {"", "one item", "items with special chars: €£¥"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("", loaded.getChoice().format(0.5)); + assertEquals("one item", loaded.getChoice().format(1.5)); + assertEquals("items with special chars: €£¥", loaded.getChoice().format(5)); + } + } + + @Test + void choiceFormatWithVeryLargeNumbers() + { + double[] limits = {0, 1000000, 1000000000}; + String[] formats = {"small", "million", "billion"}; + ChoiceFormat cf = new ChoiceFormat(limits, formats); + + ChoiceData root = new ChoiceData(cf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + ChoiceData loaded = new ChoiceData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("small", loaded.getChoice().format(999)); + assertEquals("million", loaded.getChoice().format(5000000)); + assertEquals("billion", loaded.getChoice().format(5000000000L)); + } + } + + private static class ChoiceData + { + private ChoiceFormat choice; + + public ChoiceData(ChoiceFormat choice) + { + this.choice = choice; + } + + public ChoiceData() + { + } + + public ChoiceFormat getChoice() + { + return choice; + } + + public void setChoice(ChoiceFormat choice) + { + this.choice = choice; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/text/CollatorTest.java b/integration-tests/src/test/java/test/eclipse/store/various/text/CollatorTest.java new file mode 100644 index 000000000..88bf34978 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/text/CollatorTest.java @@ -0,0 +1,585 @@ +package test.eclipse.store.various.text; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.text.Collator; +import java.util.Locale; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class CollatorTest +{ + @TempDir + Path tempDir; + + @Test + void collatorFrenchPrimaryStrength() + { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setStrength(Collator.PRIMARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare("e", "é")); + assertEquals(Collator.PRIMARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorSecondaryStrength() + { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setStrength(Collator.SECONDARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertNotEquals(0, loaded.getCollator().compare("e", "é")); + assertEquals(Collator.SECONDARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorTertiaryStrength() + { + Collator c = Collator.getInstance(Locale.GERMAN); + c.setStrength(Collator.TERTIARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertNotEquals(0, loaded.getCollator().compare("a", "A")); + assertEquals(Collator.TERTIARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorIdenticalStrength() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.IDENTICAL); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare("test", "test")); + assertNotEquals(0, loaded.getCollator().compare("test", "Test")); + assertEquals(Collator.IDENTICAL, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorWithDecomposition() + { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.CANONICAL_DECOMPOSITION, loaded.getCollator().getDecomposition()); + } + } + + @Test + void collatorWithFullDecomposition() + { + Collator c = Collator.getInstance(Locale.JAPAN); + c.setDecomposition(Collator.FULL_DECOMPOSITION); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.FULL_DECOMPOSITION, loaded.getCollator().getDecomposition()); + } + } + + @Test + void collatorWithNoDecomposition() + { + Collator c = Collator.getInstance(Locale.CHINA); + c.setDecomposition(Collator.NO_DECOMPOSITION); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.NO_DECOMPOSITION, loaded.getCollator().getDecomposition()); + } + } + + @Test + void collatorDifferentLocales() + { + Collator germanCollator = Collator.getInstance(Locale.GERMAN); + Collator japaneseCollator = Collator.getInstance(Locale.JAPANESE); + Collator arabicCollator = Collator.getInstance(new Locale("ar")); + + CollatorData root1 = new CollatorData(germanCollator); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root1, tempDir.resolve("german"))) { + storageManager.storeRoot(); + } + + CollatorData root2 = new CollatorData(japaneseCollator); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root2, tempDir.resolve("japanese"))) { + storageManager.storeRoot(); + } + + CollatorData root3 = new CollatorData(arabicCollator); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root3, tempDir.resolve("arabic"))) { + storageManager.storeRoot(); + } + + CollatorData loaded1 = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded1, tempDir.resolve("german"))) { + assertTrue(loaded1.getCollator().compare("Ä", "A") != 0); + } + + CollatorData loaded2 = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded2, tempDir.resolve("japanese"))) { + assertEquals(0, loaded2.getCollator().compare("test", "test")); + } + + CollatorData loaded3 = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded3, tempDir.resolve("arabic"))) { + assertEquals(0, loaded3.getCollator().compare("test", "test")); + } + } + + @Test + void collatorEmptyStrings() + { + Collator c = Collator.getInstance(Locale.US); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare("", "")); + } + } + + @Test + void collatorSpecialCharacters() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.PRIMARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare("café", "cafe")); + } + } + + @Test + void collatorLongStrings() + { + Collator c = Collator.getInstance(Locale.ENGLISH); + + String longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(100); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare(longText, longText)); + } + } + + @Test + void collatorUnicodeCharacters() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.IDENTICAL); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare("😀", "😀")); + assertNotEquals(0, loaded.getCollator().compare("😀", "😁")); + } + } + + @Test + void collatorCombinedStrengthAndDecomposition() + { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setStrength(Collator.SECONDARY); + c.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.SECONDARY, loaded.getCollator().getStrength()); + assertEquals(Collator.CANONICAL_DECOMPOSITION, loaded.getCollator().getDecomposition()); + } + } + + @Test + void collatorClonedInstance() + { + Collator c = Collator.getInstance(Locale.GERMAN); + c.setStrength(Collator.PRIMARY); + Collator cloned = (Collator) c.clone(); + + CollatorData root = new CollatorData(cloned); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.PRIMARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorWithNullStrings() + { + Collator c = Collator.getInstance(Locale.US); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + // Test that loaded collator is not null + assertTrue(loaded.getCollator() != null); + } + } + + @Test + void collatorWithWhitespace() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.TERTIARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertNotEquals(0, loaded.getCollator().compare("test", " test")); + assertNotEquals(0, loaded.getCollator().compare("test", "test ")); + } + } + + @Test + void collatorWithNumericStrings() + { + Collator c = Collator.getInstance(Locale.US); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertTrue(loaded.getCollator().compare("123", "456") < 0); + assertTrue(loaded.getCollator().compare("999", "100") > 0); + } + } + + @Test + void collatorWithMixedCaseStrings() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.TERTIARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertNotEquals(0, loaded.getCollator().compare("Test", "test")); + assertNotEquals(0, loaded.getCollator().compare("TEST", "test")); + } + } + + @Test + void collatorChineseLocale() + { + Collator c = Collator.getInstance(Locale.CHINA); + c.setStrength(Collator.PRIMARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.PRIMARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorRussianLocale() + { + Collator c = Collator.getInstance(new Locale("ru")); + c.setStrength(Collator.SECONDARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.SECONDARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorWithAccentedCharactersPrimary() + { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setStrength(Collator.PRIMARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(0, loaded.getCollator().compare("à", "a")); + assertEquals(0, loaded.getCollator().compare("ç", "c")); + } + } + + @Test + void collatorWithAccentedCharactersSecondary() + { + Collator c = Collator.getInstance(Locale.FRENCH); + c.setStrength(Collator.SECONDARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertNotEquals(0, loaded.getCollator().compare("à", "a")); + assertNotEquals(0, loaded.getCollator().compare("ç", "c")); + } + } + + @Test + void collatorDefaultLocale() + { + Collator c = Collator.getInstance(); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertTrue(loaded.getCollator() != null); + } + } + + @Test + void collatorWithSingleCharacter() + { + Collator c = Collator.getInstance(Locale.US); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertTrue(loaded.getCollator().compare("a", "b") < 0); + assertTrue(loaded.getCollator().compare("z", "a") > 0); + } + } + + @Test + void collatorTurkishLocale() + { + Collator c = Collator.getInstance(new Locale("tr")); + c.setStrength(Collator.TERTIARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.TERTIARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorSwedishLocale() + { + Collator c = Collator.getInstance(new Locale("sv")); + c.setStrength(Collator.PRIMARY); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.PRIMARY, loaded.getCollator().getStrength()); + } + } + + @Test + void collatorWithPunctuation() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.IDENTICAL); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertNotEquals(0, loaded.getCollator().compare("test.", "test")); + assertNotEquals(0, loaded.getCollator().compare("test!", "test?")); + } + } + + @Test + void collatorWithCombiningCharacters() + { + Collator c = Collator.getInstance(Locale.US); + c.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(Collator.CANONICAL_DECOMPOSITION, loaded.getCollator().getDecomposition()); + } + } + + @Test + void collatorWithSurrogateCharacters() + { + Collator c = Collator.getInstance(Locale.US); + c.setStrength(Collator.IDENTICAL); + + CollatorData root = new CollatorData(c); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + CollatorData loaded = new CollatorData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + // Test with mathematical bold script capital letters + assertTrue(loaded.getCollator().compare("\uD835\uDC00", "\uD835\uDC01") < 0); + } + } + + private static class CollatorData + { + private Collator collator; + + public CollatorData(Collator collator) + { + this.collator = collator; + } + + public CollatorData() + { + } + + public Collator getCollator() + { + return collator; + } + + public void setCollator(Collator collator) + { + this.collator = collator; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/text/DateFormatSymbolsTest.java b/integration-tests/src/test/java/test/eclipse/store/various/text/DateFormatSymbolsTest.java new file mode 100644 index 000000000..6601b351a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/text/DateFormatSymbolsTest.java @@ -0,0 +1,305 @@ +package test.eclipse.store.various.text; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.text.DateFormatSymbols; +import java.util.Locale; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class DateFormatSymbolsTest +{ + @TempDir + Path tempDir; + + @Test + void dateFormatSymbolsAsFieldInDataClass() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(dfs.getMonths(), loaded.getSymbols().getMonths()); + assertArrayEquals(dfs.getWeekdays(), loaded.getSymbols().getWeekdays()); + } + } + + @Test + void dateFormatSymbolsWithCustomMonths() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] customMonths = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ""}; + dfs.setShortMonths(customMonths); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(customMonths, loaded.getSymbols().getShortMonths()); + } + } + + @Test + void dateFormatSymbolsWithCustomWeekdays() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] customWeekdays = {"", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + dfs.setShortWeekdays(customWeekdays); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(customWeekdays, loaded.getSymbols().getShortWeekdays()); + } + } + + @Test + void dateFormatSymbolsWithAmPmStrings() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] amPmStrings = dfs.getAmPmStrings(); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(amPmStrings, loaded.getSymbols().getAmPmStrings()); + } + } + + @Test + void dateFormatSymbolsWithCustomAmPmStrings() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] customAmPm = {"Morning", "Evening"}; + dfs.setAmPmStrings(customAmPm); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(customAmPm, loaded.getSymbols().getAmPmStrings()); + } + } + + @Test + void dateFormatSymbolsDifferentLocales() + { + DateFormatSymbols dfsUS = new DateFormatSymbols(Locale.US); + DateFormatSymbols dfsFR = new DateFormatSymbols(Locale.FRANCE); + DateFormatSymbols dfsDE = new DateFormatSymbols(Locale.GERMANY); + + SymbolsData root1 = new SymbolsData(dfsUS); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root1, tempDir.resolve("us"))) { + storageManager.storeRoot(); + } + + SymbolsData root2 = new SymbolsData(dfsFR); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root2, tempDir.resolve("fr"))) { + storageManager.storeRoot(); + } + + SymbolsData root3 = new SymbolsData(dfsDE); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root3, tempDir.resolve("de"))) { + storageManager.storeRoot(); + } + + SymbolsData loaded1 = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded1, tempDir.resolve("us"))) { + assertArrayEquals(dfsUS.getMonths(), loaded1.getSymbols().getMonths()); + } + + SymbolsData loaded2 = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded2, tempDir.resolve("fr"))) { + assertArrayEquals(dfsFR.getMonths(), loaded2.getSymbols().getMonths()); + } + + SymbolsData loaded3 = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded3, tempDir.resolve("de"))) { + assertArrayEquals(dfsDE.getMonths(), loaded3.getSymbols().getMonths()); + } + } + + @Test + void dateFormatSymbolsWithEras() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] eras = dfs.getEras(); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(eras, loaded.getSymbols().getEras()); + } + } + + @Test + void dateFormatSymbolsWithLocalPatternChars() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String patternChars = dfs.getLocalPatternChars(); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(patternChars, loaded.getSymbols().getLocalPatternChars()); + } + } + + @Test + void dateFormatSymbolsWithCustomLocalPatternChars() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String customPatternChars = "GyMdkHmsSEDFwWahKzZ"; + dfs.setLocalPatternChars(customPatternChars); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(customPatternChars, loaded.getSymbols().getLocalPatternChars()); + } + } + + @Test + void dateFormatSymbolsJapaneseLocale() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.JAPAN); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(dfs.getMonths(), loaded.getSymbols().getMonths()); + assertArrayEquals(dfs.getWeekdays(), loaded.getSymbols().getWeekdays()); + } + } + + @Test + void dateFormatSymbolsChineseLocale() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.CHINA); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(dfs.getMonths(), loaded.getSymbols().getMonths()); + } + } + + @Test + void dateFormatSymbolsWithEmptyCustomMonths() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] emptyMonths = {"", "", "", "", "", "", "", "", "", "", "", "", ""}; + dfs.setShortMonths(emptyMonths); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(emptyMonths, loaded.getSymbols().getShortMonths()); + } + } + + @Test + void dateFormatSymbolsWithUnicodeCharacters() + { + DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + String[] unicodeMonths = {"一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ""}; + dfs.setShortMonths(unicodeMonths); + + SymbolsData root = new SymbolsData(dfs); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + SymbolsData loaded = new SymbolsData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertArrayEquals(unicodeMonths, loaded.getSymbols().getShortMonths()); + } + } + + private static class SymbolsData + { + private DateFormatSymbols symbols; + + public SymbolsData(DateFormatSymbols symbols) + { + this.symbols = symbols; + } + + public SymbolsData() + { + } + + public DateFormatSymbols getSymbols() + { + return symbols; + } + + public void setSymbols(DateFormatSymbols symbols) + { + this.symbols = symbols; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/text/DecimalFormatTest.java b/integration-tests/src/test/java/test/eclipse/store/various/text/DecimalFormatTest.java new file mode 100644 index 000000000..2ad8ab70a --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/text/DecimalFormatTest.java @@ -0,0 +1,75 @@ +package test.eclipse.store.various.text; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.text.DecimalFormat; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Disabled("https://github.com/eclipse-store/store/issues/522") +public class DecimalFormatTest +{ + @TempDir + Path tempDir; + + @Test + void decimalFormatAsFieldInDataClass() + { + DecimalFormat df = new DecimalFormat("0.###"); + double sample = 3.14159; + + DecimalData root = new DecimalData(df); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + DecimalData loaded = new DecimalData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(df.format(sample), loaded.getFormat().format(sample)); + } + } + + private static class DecimalData + { + private DecimalFormat format; + + public DecimalData(DecimalFormat format) + { + this.format = format; + } + + public DecimalData() + { + } + + public DecimalFormat getFormat() + { + return format; + } + + public void setFormat(DecimalFormat format) + { + this.format = format; + } + } +} + diff --git a/integration-tests/src/test/java/test/eclipse/store/various/text/MessageFormatTest.java b/integration-tests/src/test/java/test/eclipse/store/various/text/MessageFormatTest.java new file mode 100644 index 000000000..44c2cc407 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/various/text/MessageFormatTest.java @@ -0,0 +1,248 @@ +package test.eclipse.store.various.text; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.Date; +import java.util.Locale; + +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class MessageFormatTest +{ + @TempDir + Path tempDir; + + @Test + void messageFormatAsFieldInDataClass() + { + MessageFormat mf = new MessageFormat("Hello {0}"); + Object[] params = new Object[]{"World"}; + + MessageData root = new MessageData(mf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + MessageData loaded = new MessageData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(mf.format(params), loaded.getFormat().format(params)); + } + } + + @Test + void messageFormatWithMultipleParameters() + { + MessageFormat mf = new MessageFormat("User {0} has {1} messages and {2} notifications"); + Object[] params = new Object[]{"Alice", 5, 3}; + + MessageData root = new MessageData(mf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + MessageData loaded = new MessageData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals("User Alice has 5 messages and 3 notifications", loaded.getFormat().format(params)); + } + } + + @Test + @Disabled("https://github.com/eclipse-serializer/serializer/issues/236") + void messageFormatWithNumberFormat() + { + MessageFormat mf = new MessageFormat("The price is {0,number,currency}", Locale.US); + Object[] params = new Object[]{123.45}; + + MessageData root = new MessageData(mf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + MessageData loaded = new MessageData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(mf.format(params), loaded.getFormat().format(params)); + } + } + + @Test + @Disabled("https://github.com/eclipse-serializer/serializer/issues/235") + void messageFormatWithDateFormat() + { + MessageFormat mf = new MessageFormat("Today is {0,date,long}", Locale.US); + Object[] params = new Object[]{new Date(1610000000000L)}; + + MessageData root = new MessageData(mf); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(root, tempDir)) { + storageManager.storeRoot(); + } + + MessageData loaded = new MessageData(); + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(loaded, tempDir)) { + assertEquals(mf.format(params), loaded.getFormat().format(params)); + } + } + + @Test + void messageFormatWithChoiceFormat() + { + MessageFormat mf = new MessageFormat("There {0,choice,0#are no files|1#is one file|1Disabled in CI because the scenario uses Java GC + WeakReference + * timing (~13 s on a developer machine) and is non-deterministic enough + * that CI noise could destabilise it. Run locally to confirm the fix's + * scaled behaviour. + */ +@Disabled("Slow (~13 s) and timing-sensitive due to System.gc() reliance; " + + "scaled reproducer of the registry-safety-net zombie pattern. " + + "The single-holder variant (RegistrySafetyNetZombieTest) is the " + + "canonical CI regression for the same root cause.") +public class LargeDetachedTreeTest +{ + @TempDir + Path tempDir; + + EmbeddedStorageManager storage; + EmbeddedStorageManager reloaded; + + @AfterEach + public void afterTest() + { + if (this.reloaded != null) { + try { + this.reloaded.shutdown(); + } catch (final Exception ignored) { + } + } + if (this.storage != null && this.storage.isRunning()) { + try { + this.storage.shutdown(); + } catch (final Exception ignored) { + } + } + } + + /** + * Build a tree of 50 branches × 20 leaves; detach the entire container + * from root binary; clear half of the lazies; reap their targets; + * run several GC cycles; re-attach; reload + verify all data preserved. + * + *

Baseline: 500 zombie OIDs after re-attach. Fix: 0. + */ + @Test + void largeSubtreeWithMidLazyClearsAndCleanupPreservesData() throws Exception + { + final CountingZombieOidHandler zombieHandler = new CountingZombieOidHandler(); + + this.storage = newStorage(zombieHandler); + final PersistenceObjectRegistry registry = this.storage.persistenceManager().objectRegistry(); + + final ApplicationRoot appRoot = new ApplicationRoot(); + final Container container = new Container("clearTestContainer"); + for (int b = 0; b < 50; b++) { + final Branch branch = new Branch("branch-" + b); + for (int l = 0; l < 20; l++) { + branch.leaves.add(Lazy.Reference(new Leaf("leaf-" + b + "-" + l, b * 100 + l))); + } + container.branches.add(branch); + } + appRoot.container = container; + this.storage.setRoot(appRoot); + this.storage.storeRoot(); + + // Probes for selected leaves so we can verify Java GC cleared them. + final List> probes = new ArrayList<>(); + for (int b = 0; b < 50; b += 2) { + for (int l = 0; l < 20; l += 2) { + @SuppressWarnings("unchecked") final Lazy lazy = (Lazy) container.branches.get(b).leaves.get(l); + probes.add(new WeakReference<>(lazy.get())); + } + } + + // Detach the container from root binary; keep Java reference. + final Container heldContainer = appRoot.container; + appRoot.container = null; + this.storage.storeRoot(); + + // Clear half the lazies (every 2nd branch, every 2nd leaf within). + for (int b = 0; b < 50; b += 2) { + for (int l = 0; l < 20; l += 2) { + Lazy.clear(heldContainer.branches.get(b).leaves.get(l)); + } + } + + // Force Java GC and registry cleanup. + for (int i = 0; i < 30; i++) { + boolean allCleared = true; + for (final WeakReference p : probes) { + if (p.get() != null) { + allCleared = false; + break; + } + } + if (allCleared) { + break; + } + System.gc(); + Thread.sleep(50); + } + long stillAlive = 0; + for (final WeakReference p : probes) { + if (p.get() != null) { + stillAlive++; + } + } + assumeTrue(stillAlive < probes.size() / 2, + "JVM did not GC enough leaves to make this test meaningful (still alive: " + + stillAlive + " of " + probes.size() + ")"); + + registry.cleanUp(); + + // GC cycles. Pre-sweep gate must seed Container + Branch + remaining + // Lazy entities; mark walks transitively to leaves; sweep keeps them. + for (int i = 0; i < 3; i++) { + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, zombieHandler.count(), + "No zombies during cleared-lazies GC iter=" + i + + ", got " + zombieHandler.oids()); + } + + // Re-attach and reload. + appRoot.container = heldContainer; + this.storage.storeRoot(); + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, zombieHandler.count(), + "No zombies after re-attach with cleared lazies"); + + this.storage.shutdown(); + + final CountingZombieOidHandler reloadHandler = new CountingZombieOidHandler(); + this.reloaded = newStorage(reloadHandler); + final ApplicationRoot reloadedRoot = (ApplicationRoot) this.reloaded.root(); + + for (int b = 0; b < 50; b++) { + final Branch branch = reloadedRoot.container.branches.get(b); + for (int l = 0; l < 20; l++) { + @SuppressWarnings("unchecked") final Lazy lazy = (Lazy) branch.leaves.get(l); + final Leaf leaf = lazy.get(); + assertNotNull(leaf, + "Leaf " + b + "-" + l + " must reload (data-loss check)"); + assertEquals(b * 100 + l, leaf.value); + } + } + + this.reloaded.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, reloadHandler.count(), + "No zombies after reload + final GC"); + } + + private EmbeddedStorageManager newStorage(final CountingZombieOidHandler handler) + { + return EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setChannelCountProvider(Storage.ChannelCountProvider(1)) + .setHousekeepingController(Storage.HousekeepingController(50, 100_000_000)) + .setDataFileEvaluator(Storage.DataFileEvaluator(1024, 8192, 1.0)) + .setStorageFileProvider(Storage.FileProvider(this.tempDir)) + .createConfiguration() + ) + .setGCZombieOidHandler(handler) + .start(); + } + + /////////////////////////////////////////////////////////////////////////// + // data types // + + /// //////////// + + public static class Leaf + { + public String label; + public int value; + + public Leaf(final String label, final int value) + { + super(); + this.label = label; + this.value = value; + } + } + + public static class Branch + { + public String label; + public List> leaves = new ArrayList<>(); + + public Branch(final String label) + { + super(); + this.label = label; + } + } + + public static class Container + { + public String label; + public List branches = new ArrayList<>(); + + public Container(final String label) + { + super(); + this.label = label; + } + } + + public static class ApplicationRoot + { + public Container container; + } + + static final class CountingZombieOidHandler implements StorageGCZombieOidHandler + { + final AtomicInteger zombieCount = new AtomicInteger(); + final List zombieOids = new ArrayList<>(); + + @Override + public boolean handleZombieOid(final long objectId) + { + this.zombieCount.incrementAndGet(); + synchronized (this.zombieOids) { + this.zombieOids.add(objectId); + } + return true; + } + + public int count() + { + return this.zombieCount.get(); + } + + public List oids() + { + synchronized (this.zombieOids) { + return new ArrayList<>(this.zombieOids); + } + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/zombie/RegistrySafetyNetLazyZombieTest.java b/integration-tests/src/test/java/test/eclipse/store/zombie/RegistrySafetyNetLazyZombieTest.java new file mode 100644 index 000000000..e201284db --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/zombie/RegistrySafetyNetLazyZombieTest.java @@ -0,0 +1,237 @@ +package test.eclipse.store.zombie; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.lang.ref.WeakReference; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.persistence.types.PersistenceObjectRegistry; +import org.eclipse.serializer.reference.Lazy; +import org.eclipse.serializer.reference.Swizzling; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.eclipse.store.storage.types.StorageGCZombieOidHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Lazy-aware variant of {@code RegistrySafetyNetZombieTest}: the safety-net + * entity is a {@link Lazy} wrapping a chain {@code Lazy → Holder → Payload}. + * Validates that the mark-seed fix marks transitively through a Lazy entity + * — i.e. when a Lazy is kept alive only by the registry safety net, its + * stored binary record (which contains the target OID) is walked by mark + * before sweep deletes the target. + * + *

Pass condition: no zombie OIDs and the reloaded graph is intact + * ({@code root.lazyHolder.get().payload != null}). + */ +public class RegistrySafetyNetLazyZombieTest +{ + @TempDir + Path tempDir; + + EmbeddedStorageManager storage; + EmbeddedStorageManager reloaded; + + @AfterEach + public void afterTest() + { + if (this.reloaded != null) { + try { + this.reloaded.shutdown(); + } catch (final Exception ignored) { /* best effort */ } + } + if (this.storage != null && this.storage.isRunning()) { + try { + this.storage.shutdown(); + } catch (final Exception ignored) { /* best effort */ } + } + } + + @Test + void lazyHolderSurvivingViaSafetyNetDoesNotProduceZombieOids() throws Exception + { + final CountingZombieOidHandler zombieHandler = new CountingZombieOidHandler(); + + // Phase 1: start storage, store root -> Lazy -> Holder -> Payload + this.storage = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setChannelCountProvider(Storage.ChannelCountProvider(1)) + .setHousekeepingController(Storage.HousekeepingController(100, 1_000_000_000)) + .setDataFileEvaluator(Storage.DataFileEvaluator(1024, 2048, 1.0)) + .setStorageFileProvider(Storage.FileProvider(this.tempDir)) + .createConfiguration() + ) + .setGCZombieOidHandler(zombieHandler) + .start(); + + final PersistenceObjectRegistry registry = this.storage.persistenceManager().objectRegistry(); + + final DataRoot root = new DataRoot(); + final long holderOid; + final long payloadOid; + final WeakReference holderProbe; + { + final Payload originalPayload = new Payload("ghost-via-lazy"); + final Holder originalHolder = new Holder(originalPayload); + root.lazyHolder = Lazy.Reference(originalHolder); + this.storage.setRoot(root); + this.storage.storeRoot(); + + holderOid = registry.lookupObjectId(originalHolder); + payloadOid = registry.lookupObjectId(originalPayload); + holderProbe = new WeakReference<>(originalHolder); + } + assertNotEquals(Swizzling.notFoundId(), holderOid, "Holder must be registered"); + assertNotEquals(Swizzling.notFoundId(), payloadOid, "Payload must be registered"); + + // Phase 2: detach the Lazy from root in binary, keep Java ref to the Lazy itself. + // This puts the Lazy entity in the registry-safety-net role. + @SuppressWarnings("unchecked") final Lazy lazyRef = (Lazy) root.lazyHolder; + root.lazyHolder = null; + this.storage.storeRoot(); + + // Phase 3: clear the Lazy (drops its strong subject reference) and reap the + // Holder and Payload registry entries. The Lazy stays in the registry because + // the test still holds a strong Java reference to lazyRef. + Lazy.clear(lazyRef); + + for (int i = 0; i < 10 && holderProbe.get() != null; i++) { + System.gc(); + Thread.sleep(50); + } + assumeTrue(holderProbe.get() == null, + "JVM did not garbage-collect the Holder — test cannot proceed deterministically"); + + // Reap cleared WeakReference entries. + registry.cleanUp(); + + // Phase 4: GC cycle 1. + // With the fix: pre-sweep gate enqueues the Lazy's OID, mark walks the Lazy's + // binary → Holder OID → enqueue → mark walks Holder's binary → Payload OID + // → enqueue → all three transitively marked → none swept. + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + + // Phase 5: re-attach Lazy to root. Lazy storer skips re-storing (already registered). + root.lazyHolder = lazyRef; + this.storage.storeRoot(); + + // Phase 6: GC cycle 2. + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + + assertEquals(0, zombieHandler.count(), + "No zombie OIDs expected — got " + zombieHandler.count() + ": " + zombieHandler.oids()); + + // Phase 7: shutdown and reload; verify the persisted graph is intact. + this.storage.shutdown(); + + final CountingZombieOidHandler reloadZombieHandler = new CountingZombieOidHandler(); + this.reloaded = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setStorageFileProvider(Storage.FileProvider(this.tempDir)) + .createConfiguration() + ) + .setGCZombieOidHandler(reloadZombieHandler) + .start(); + + final DataRoot reloadedRoot = (DataRoot) this.reloaded.root(); + assertNotNull(reloadedRoot, "Reloaded root must not be null"); + assertNotNull(reloadedRoot.lazyHolder, "Reloaded lazyHolder must not be null"); + + @SuppressWarnings("unchecked") final Lazy reloadedLazy = (Lazy) reloadedRoot.lazyHolder; + final Holder reloadedHolder = reloadedLazy.get(); + assertNotNull(reloadedHolder, "Reloaded Holder via Lazy.get() must not be null"); + assertNotNull(reloadedHolder.payload, "Reloaded Payload must not be null"); + assertEquals("ghost-via-lazy", reloadedHolder.payload.data); + + this.reloaded.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, reloadZombieHandler.count(), + "No zombie OIDs expected on reloaded storage — got " + reloadZombieHandler.count() + + ": " + reloadZombieHandler.oids()); + } + + /////////////////////////////////////////////////////////////////////////// + // data types // + + /// //////////// + + public static class Holder + { + public Payload payload; + + public Holder(final Payload payload) + { + super(); + this.payload = payload; + } + } + + public static class Payload + { + public final String data; + + public Payload(final String data) + { + super(); + this.data = data; + } + } + + public static class DataRoot + { + // Stored as a generic Lazy reference to keep the test data class field-compatible + // with the runtime Lazy type produced by Lazy.Reference(...). + public Lazy lazyHolder; + } + + static final class CountingZombieOidHandler implements StorageGCZombieOidHandler + { + final AtomicInteger zombieCount = new AtomicInteger(); + final List zombieOids = new ArrayList<>(); + + @Override + public boolean handleZombieOid(final long objectId) + { + this.zombieCount.incrementAndGet(); + synchronized (this.zombieOids) { + this.zombieOids.add(objectId); + } + return true; + } + + public int count() + { + return this.zombieCount.get(); + } + + public List oids() + { + synchronized (this.zombieOids) { + return new ArrayList<>(this.zombieOids); + } + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/zombie/RegistrySafetyNetZombieTest.java b/integration-tests/src/test/java/test/eclipse/store/zombie/RegistrySafetyNetZombieTest.java new file mode 100644 index 000000000..0be7071bf --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/zombie/RegistrySafetyNetZombieTest.java @@ -0,0 +1,252 @@ +package test.eclipse.store.zombie; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.lang.ref.WeakReference; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.persistence.types.PersistenceObjectRegistry; +import org.eclipse.serializer.reference.Swizzling; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.eclipse.store.storage.types.StorageGCZombieOidHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * JUnit port of {@code RegistrySafetyNetZombieDemo}. Exercises the corner + * case described in {@code store/storage/storage/GC.md} §9: an entity that + * survives a sweep only via the registry safety net retains its old + * binary record; if that binary references another entity whose Java object + * was collected and whose registry entry was reaped, the referenced entity + * is swept. Without the {@code LiveObjectIdsIterator} mark-seed (§10) the + * next mark cycle would surface a zombie OID. + * + *

Pass condition: the GC produces no zombie OIDs and the reloaded graph + * is intact (Payload reachable via {@code root.holder.payload}). + */ +public class RegistrySafetyNetZombieTest +{ + @TempDir + Path tempDir; + + EmbeddedStorageManager storage; + EmbeddedStorageManager reloaded; + + @AfterEach + public void afterTest() + { + if (this.reloaded != null) { + try { + this.reloaded.shutdown(); + } catch (final Exception ignored) { /* best effort */ } + } + if (this.storage != null && this.storage.isRunning()) { + try { + this.storage.shutdown(); + } catch (final Exception ignored) { /* best effort */ } + } + } + + @Test + void registrySafetyNetDoesNotProduceZombieOidOrCorruption() throws Exception + { + final CountingZombieOidHandler zombieHandler = new CountingZombieOidHandler(); + + // Phase 1: start storage, store root -> Holder -> Payload + this.storage = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setChannelCountProvider(Storage.ChannelCountProvider(1)) + .setHousekeepingController(Storage.HousekeepingController(100, 1_000_000_000)) + .setDataFileEvaluator(Storage.DataFileEvaluator(1024, 2048, 1.0)) + .setStorageFileProvider(Storage.FileProvider(this.tempDir)) + .createConfiguration() + ) + .setGCZombieOidHandler(zombieHandler) + .start(); + + final PersistenceObjectRegistry registry = this.storage.persistenceManager().objectRegistry(); + + final DataRoot root = new DataRoot(); + final long payloadOid; + final WeakReference payloadProbe; + { + // Scope the only strong Payload reference to this block so it can + // become eligible for GC after Phase 3 nulls Holder.payload. + final Payload originalPayload = new Payload("I will become a ghost"); + root.holder = new Holder(originalPayload); + this.storage.setRoot(root); + this.storage.storeRoot(); + + payloadOid = registry.lookupObjectId(originalPayload); + payloadProbe = new WeakReference<>(originalPayload); + } + assertNotEquals(Swizzling.notFoundId(), payloadOid, + "Payload must be registered after initial store"); + + // Phase 2: detach Holder from root in binary, keep Java ref alive + final Holder holderRef = root.holder; + root.holder = null; + this.storage.storeRoot(); + + // Phase 3: drop the last strong Payload reference and reap its registry entry. + holderRef.payload = null; + + for (int i = 0; i < 10 && payloadProbe.get() != null; i++) { + System.gc(); + Thread.sleep(50); + } + assumeTrue(payloadProbe.get() == null, + "JVM did not garbage-collect the Payload — test cannot proceed deterministically"); + + // Reap the cleared WeakReference Entry from the registry hash table. + // Same code path that synchInternalMergeEntries triggers on every store + // (see GC.md §8); calling it directly avoids the sizing noise of an + // intermediate store() call. + registry.cleanUp(); + + // Phase 4: GC cycle 1 — Payload swept; Holder kept alive by safety net, + // its stored binary still references Payload's now-stale OID. + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + + // Phase 5: re-attach Holder to root; lazy storer skips re-storing the + // already-registered Holder, so its stale binary record persists. + root.holder = holderRef; + this.storage.storeRoot(); + + // Phase 6: GC cycle 2 — without the LiveObjectIdsIterator mark-seed + // (GC.md §10), marking Holder's stale binary would surface Payload's + // swept OID as a zombie here. + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + + assertEquals(0, zombieHandler.count(), + "No zombie OIDs expected after the second GC cycle, but got " + + zombieHandler.count() + ": " + zombieHandler.oids()); + + // Phase 7: shut down and reload to confirm the persisted graph is intact. + this.storage.shutdown(); + + final CountingZombieOidHandler reloadZombieHandler = new CountingZombieOidHandler(); + this.reloaded = EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setStorageFileProvider(Storage.FileProvider(this.tempDir)) + .createConfiguration() + ) + .setGCZombieOidHandler(reloadZombieHandler) + .start(); + + final DataRoot reloadedRoot = (DataRoot) this.reloaded.root(); + assertNotNull(reloadedRoot, "Reloaded root must not be null"); + assertNotNull(reloadedRoot.holder, "Reloaded holder must not be null"); + assertNotNull(reloadedRoot.holder.payload, + "Reloaded Payload must not be null — losing it means a zombie OID corrupted the store"); + assertEquals("I will become a ghost", reloadedRoot.holder.payload.data, + "Reloaded Payload data must match the originally stored value"); + + this.reloaded.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, reloadZombieHandler.count(), + "No zombie OIDs expected on reloaded storage, but got " + + reloadZombieHandler.count() + ": " + reloadZombieHandler.oids()); + } + + /////////////////////////////////////////////////////////////////////////// + // data types // + + /// //////////// + + public static class Holder + { + public Payload payload; + + public Holder(final Payload payload) + { + super(); + this.payload = payload; + } + + @Override + public String toString() + { + return "Holder[payload=" + this.payload + "]"; + } + } + + public static class Payload + { + public final String data; + + public Payload(final String data) + { + super(); + this.data = data; + } + + @Override + public String toString() + { + return "Payload[" + this.data + "]"; + } + } + + public static class DataRoot + { + public Holder holder; + + @Override + public String toString() + { + return "DataRoot[holder=" + this.holder + "]"; + } + } + + static final class CountingZombieOidHandler implements StorageGCZombieOidHandler + { + final AtomicInteger zombieCount = new AtomicInteger(); + final List zombieOids = new ArrayList<>(); + + @Override + public boolean handleZombieOid(final long objectId) + { + this.zombieCount.incrementAndGet(); + synchronized (this.zombieOids) { + this.zombieOids.add(objectId); + } + return true; + } + + public int count() + { + return this.zombieCount.get(); + } + + public List oids() + { + synchronized (this.zombieOids) { + return new ArrayList<>(this.zombieOids); + } + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/zombie/StorageGrowthBoundaryTest.java b/integration-tests/src/test/java/test/eclipse/store/zombie/StorageGrowthBoundaryTest.java new file mode 100644 index 000000000..d96d54d78 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/zombie/StorageGrowthBoundaryTest.java @@ -0,0 +1,246 @@ +package test.eclipse.store.zombie; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.serializer.persistence.types.PersistenceObjectRegistry; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.Storage; +import org.eclipse.store.storage.types.StorageGCZombieOidHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Boundary-at-scale reproducer: 5000 holders force the registry hash table + * to rebuild multiple times during the lifetime of an orphan-but-Java-alive + * pattern. Pre-sweep gate iteration walks every bucket of the rebuilt table; + * if any entity were missed, mark seeding would skip it and sweep would + * delete its referenced payload, producing zombies on the next cycle. + * + *

On baseline the test fails with ~2500 zombie OIDs after re-attach. + * On the fix it produces 0 zombies and reload preserves all 5000 entries. + * + *

Disabled in CI: the test relies on {@code System.gc()} timing for the + * Java GC + registry cleanUp phase and runs ~5 s per cycle. The + * single-holder canonical test (RegistrySafetyNetZombieTest) covers the + * same root cause for CI. + */ +@Disabled("Slow (~24 s) and timing-sensitive; scaled (5000-entity) variant " + + "of the registry-safety-net zombie pattern that exercises hash-table " + + "rebuild boundaries. The single-holder variant " + + "(RegistrySafetyNetZombieTest) is the canonical CI regression for " + + "the same root cause.") +public class StorageGrowthBoundaryTest +{ + @TempDir + Path tempDir; + + EmbeddedStorageManager storage; + EmbeddedStorageManager reloaded; + + @AfterEach + public void afterTest() + { + if (this.reloaded != null) { + try { + this.reloaded.shutdown(); + } catch (final Exception ignored) { + } + } + if (this.storage != null && this.storage.isRunning()) { + try { + this.storage.shutdown(); + } catch (final Exception ignored) { + } + } + } + + @Test + void registryGrowthAcrossHashTableRebuildBoundariesPreservesAllEntities() throws Exception + { + final CountingZombieOidHandler zombieHandler = new CountingZombieOidHandler(); + + this.storage = newStorage(zombieHandler); + final PersistenceObjectRegistry registry = this.storage.persistenceManager().objectRegistry(); + + // Build the graph BEFORE the first setRoot/storeRoot so the initial + // deep store captures all entities. Eclipse Store's storeRoot is shallow + // (does not propagate mutations to referenced collections); explicit + // store(root.entries) is needed for any later mutation. + final int N = 5000; + final List holders = new ArrayList<>(N); + final Root root = new Root(); + + for (int i = 0; i < N; i++) { + final Holder h = new Holder(new Payload("v-" + i, i)); + holders.add(h); + root.entries.add(h); + } + this.storage.setRoot(root); + this.storage.storeRoot(); + + final long preGrowthSize = registry.size(); + + // Settle once. + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + + // Detach half the holders from root binary; keep Java refs. + final List detachedAlive = new ArrayList<>(); + for (int i = 0; i < N; i += 2) { + detachedAlive.add(holders.get(i)); + } + root.entries.removeIf(detachedAlive::contains); + this.storage.storeRoot(); + this.storage.store(root.entries); // propagate the list mutation + + // Drop java refs to the payloads of half the detached holders. + for (int i = 0; i < detachedAlive.size(); i += 2) { + detachedAlive.get(i).payload = null; + } + + for (int i = 0; i < 20; i++) { + System.gc(); + Thread.sleep(50); + } + registry.cleanUp(); + + final long postCleanupSize = registry.size(); + assumeTrue(postCleanupSize < preGrowthSize, + "JVM did not GC enough payloads (cleanUp removed nothing); test cannot proceed"); + + // Several GC cycles — pre-sweep gate iterates the resized table each + // time. With ~7500 still-live OIDs (root + remaining holders + + // detachedAlive Java refs), iteration must visit all of them. + for (int i = 0; i < 3; i++) { + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, zombieHandler.count(), + "No zombies expected during large-registry GC, iter=" + i + + ", got " + zombieHandler.oids()); + } + + // Re-attach all holders so reload verification finds them. + for (final Holder h : detachedAlive) { + if (!root.entries.contains(h)) { + root.entries.add(h); + } + } + this.storage.storeRoot(); + this.storage.store(root.entries); // propagate list mutation + this.storage.issueFullGarbageCollection(); + Thread.sleep(200); + assertEquals(0, zombieHandler.count(), "No zombies after re-attach + GC"); + + // Reload + verify N entities recovered. + this.storage.shutdown(); + final CountingZombieOidHandler reloadHandler = new CountingZombieOidHandler(); + this.reloaded = newStorage(reloadHandler); + final Root reloadedRoot = (Root) this.reloaded.root(); + assertEquals(N, reloadedRoot.entries.size(), + "All N=" + N + " entries must reload"); + for (int i = 0; i < N; i++) { + final Holder h = reloadedRoot.entries.get(i); + assertNotNull(h, "Reloaded holder " + i + " must not be null"); + } + } + + private EmbeddedStorageManager newStorage(final CountingZombieOidHandler handler) + { + return EmbeddedStorage.Foundation( + Storage.ConfigurationBuilder() + .setChannelCountProvider(Storage.ChannelCountProvider(1)) + .setHousekeepingController(Storage.HousekeepingController(50, 100_000_000)) + .setDataFileEvaluator(Storage.DataFileEvaluator(1024, 8192, 1.0)) + .setStorageFileProvider(Storage.FileProvider(this.tempDir)) + .createConfiguration() + ) + .setGCZombieOidHandler(handler) + .start(); + } + + /////////////////////////////////////////////////////////////////////////// + // data types // + + /// //////////// + + public static class Holder + { + public Payload payload; + + public Holder(final Payload payload) + { + super(); + this.payload = payload; + } + } + + public static class Payload + { + public String label; + public int value; + + public Payload(final String label, final int value) + { + super(); + this.label = label; + this.value = value; + } + } + + public static class Root + { + public List entries = new ArrayList<>(); + } + + static final class CountingZombieOidHandler implements StorageGCZombieOidHandler + { + final AtomicInteger zombieCount = new AtomicInteger(); + final List zombieOids = new ArrayList<>(); + + @Override + public boolean handleZombieOid(final long objectId) + { + this.zombieCount.incrementAndGet(); + synchronized (this.zombieOids) { + this.zombieOids.add(objectId); + } + return true; + } + + public int count() + { + return this.zombieCount.get(); + } + + public List oids() + { + synchronized (this.zombieOids) { + return new ArrayList<>(this.zombieOids); + } + } + } +} diff --git a/integration-tests/src/test/java/test/eclipse/store/zombie/StorerWithIdTest.java b/integration-tests/src/test/java/test/eclipse/store/zombie/StorerWithIdTest.java new file mode 100644 index 000000000..2732d6a93 --- /dev/null +++ b/integration-tests/src/test/java/test/eclipse/store/zombie/StorerWithIdTest.java @@ -0,0 +1,240 @@ +package test.eclipse.store.zombie; + +/*- + * #%L + * EclipseStore Integration Tests + * %% + * Copyright (C) 2023 - 2026 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.eclipse.serializer.persistence.exceptions.PersistenceExceptionConsistencyObjectId; +import org.eclipse.serializer.persistence.types.Storer; +import org.eclipse.store.storage.analysis.*; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.eclipse.store.storage.types.StorageAdjacencyDataExporter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import test.eclipse.serializer.fixtures.TypeEnum; + +public class StorerWithIdTest +{ + @TempDir + Path tempDir; + + public static String value = "hello"; + + @Test + void saveNewObject() + { + String root = "ahoj"; + + Root newRoot = new Root(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(newRoot, tempDir)) { + + long l = storageManager.persistenceManager().objectRegistry().lookupObjectId(newRoot); + + Storer storer = storageManager.createStorer(); + long storeId = storer.store(root, l + 10); + assertEquals(storeId, l + 10); + storer.commit(); + + boolean hello = storageManager.persistenceManager().objectRegistry().isValid(storeId, root); + assertTrue(hello, "Object should be valid after storing"); + } + + } + + @Test + void saveExistingObject_Exception() + { + Root newRoot = new Root(); + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(newRoot, tempDir)) { + + long l = storageManager.persistenceManager().objectRegistry().lookupObjectId(newRoot); + + Storer storer = storageManager.createStorer(); + long storeId = storer.store(newRoot, l + 10); + assertEquals(storeId, l + 10); + assertThrows(PersistenceExceptionConsistencyObjectId.class, storer::commit); + + + long store = storageManager.store(newRoot); + assertEquals(l, store, "Stored ID should match original object ID"); + + boolean b = storageManager.persistenceManager().objectRegistry().containsObjectId(1000000000000000038L); + assertFalse(b, "Object should not be valid after storing with different ID"); + + } + + } + + @Test + void saveSameObjectAgain() + { + Root newRoot = new Root(); + String root = "ahoj"; + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(newRoot, tempDir)) { + + long l = storageManager.persistenceManager().objectRegistry().lookupObjectId(newRoot); + + Storer storer = storageManager.createStorer(); + long storeId = storer.store(newRoot, l); + assertEquals(storeId, l, "Stored ID should match original object ID"); + storer.commit(); + + } + + } + + @Test + void saveAnotherObjectOnTheSameId_Exception() + { + Root newRoot = new Root(); + String root = "hello"; + + try (EmbeddedStorageManager storageManager = EmbeddedStorage.start(newRoot, tempDir)) { + + long l = storageManager.persistenceManager().objectRegistry().lookupObjectId(newRoot); + + Storer storer = storageManager.createStorer(); + long storeId = storer.store(root, l); + assertEquals(storeId, l, "Stored ID should match original object ID"); + assertThrows(PersistenceExceptionConsistencyObjectId.class, storer::commit); + } + + } + + @Test + void findMissingObject(@TempDir Path exportDir) + { + ListRoot newRoot = new ListRoot(); + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(newRoot, tempDir)) { + final List exports = storage.exportAdjacencyData(exportDir); + + final AdjacencyDataConverter dataPreparator = AdjacencyDataConverter.New(exports); + final AdjacencyDataConverter.ConvertedAdjacencyFiles data = dataPreparator.convert(); + + final MissingObjectsSearch analyser = MissingObjectsSearch.New(exports, data.getReferenceSets(), null); + final MissingObjects missingEntities = analyser.searchMissingEntities(); + + Assertions.assertEquals(0, missingEntities.getMissingObjectIDs().size()); + + final ReverseObjectSearch reverseObjectSearch = ReverseObjectSearch.New(exports, data); + Long objectId = storage.persistenceManager().objectRegistry().lookupObjectId(newRoot.getList().get(3)); + + ObjectParents objectParents = reverseObjectSearch.searchObjectIDs(Set.of(objectId)); + + Arrays.stream(objectParents.getParents(objectId)).forEach((id) -> { + Object o = storage.persistenceManager().objectRegistry().lookupObject(id); + assertSame(newRoot.getList(), o); + }); + + + } + + } + + @ParameterizedTest() + @EnumSource(TypeEnum.class) + void name(TypeEnum type, @TempDir Path exportDir) + { + + try (final EmbeddedStorageManager storage = EmbeddedStorage.start(type.getOriginal(), tempDir)) { + final List exports = storage.exportAdjacencyData(exportDir); + + final AdjacencyDataConverter dataPreparator = AdjacencyDataConverter.New(exports); + final AdjacencyDataConverter.ConvertedAdjacencyFiles data = dataPreparator.convert(); + + + final MissingObjectsSearch analyser = MissingObjectsSearch.New(exports, data.getReferenceSets(), null); + final MissingObjects missingEntities = analyser.searchMissingEntities(); + + assertEquals(0, missingEntities.getMissingObjectIDs().size()); +// missingEntities.getMissingObjectIDs().forEach(aLong -> { +// Object o = storage.persistenceManager().objectRegistry().lookupObject(aLong); +// System.out.println("Missing Objects: " + o); +// }); + + long l1 = storage.persistenceManager().currentObjectId(); + + final ReverseObjectSearch reverseObjectSearch = ReverseObjectSearch.New(exports, data); + ObjectParents objectParents = reverseObjectSearch.searchObjectIDs(Set.of(l1)); + + assertNotEquals(0, objectParents.getParents(l1).length); + +// long l = storage.persistenceManager().objectRegistry().lookupObjectId(type); +// System.out.println(l); +// +// System.out.println("Current Object ID: " + l1); +// Object o = storage.persistenceManager().objectRegistry().lookupObject(l1); +// System.out.println("Current Object: " + o); +// +// long[] parents = objectParents.getParents(l1); +// System.out.println("Parents of current object ID: " + Arrays.toString(parents)); +// +// System.out.println("=========================================================="); + + + } + + } + + + private static class ListRoot + { + List list = new ArrayList<>(); + String s = "hello"; + + public ListRoot() + { + list.add(s); + list.add("world"); + list.add("test"); + list.add("microstream"); + list.add("zombie"); + } + + @Override + public String toString() + { + return "ListRoot{" + + "list=" + list + + ", s='" + s + '\'' + + '}'; + } + + public List getList() + { + return list; + } + } + + private static class Root + { + String root = value; + + } +} diff --git a/integration-tests/src/test/resources/configuration/channelCount4.ini b/integration-tests/src/test/resources/configuration/channelCount4.ini new file mode 100644 index 000000000..1cb97acfe --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelCount4.ini @@ -0,0 +1 @@ +channel-count = 4 \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelCount4.json b/integration-tests/src/test/resources/configuration/channelCount4.json new file mode 100644 index 000000000..d2dd93a60 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelCount4.json @@ -0,0 +1,3 @@ +{ + "channel-count": 4 +} \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelCount4.yml b/integration-tests/src/test/resources/configuration/channelCount4.yml new file mode 100644 index 000000000..6a63d54e0 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelCount4.yml @@ -0,0 +1 @@ +channel-count: 4 \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelCount64.ini b/integration-tests/src/test/resources/configuration/channelCount64.ini new file mode 100644 index 000000000..8e2b754a7 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelCount64.ini @@ -0,0 +1 @@ +channel-count = 64 \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelCount64.yml b/integration-tests/src/test/resources/configuration/channelCount64.yml new file mode 100644 index 000000000..75912155b --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelCount64.yml @@ -0,0 +1 @@ +channel-count: 64 \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.ini b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.ini new file mode 100644 index 000000000..bf9379def --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.ini @@ -0,0 +1 @@ +channel-directory-prefix = channelXX_ \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.json b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.json new file mode 100644 index 000000000..4e3e73458 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.json @@ -0,0 +1,3 @@ +{ + "channel-directory-prefix": "channelXX_" +} diff --git a/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.xml b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.xml new file mode 100644 index 000000000..86bf19c79 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.yml b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.yml new file mode 100644 index 000000000..de27ef53d --- /dev/null +++ b/integration-tests/src/test/resources/configuration/channelDirectoryPrefix.yml @@ -0,0 +1 @@ +channel-directory-prefix: channelXX_ \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/dataFilePrefix.ini b/integration-tests/src/test/resources/configuration/dataFilePrefix.ini new file mode 100644 index 000000000..62f786d9e --- /dev/null +++ b/integration-tests/src/test/resources/configuration/dataFilePrefix.ini @@ -0,0 +1 @@ +data-file-prefix = database_ \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/dataFilePrefix.xml b/integration-tests/src/test/resources/configuration/dataFilePrefix.xml new file mode 100644 index 000000000..1c4e40083 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/dataFilePrefix.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/dataFilePrefix.yml b/integration-tests/src/test/resources/configuration/dataFilePrefix.yml new file mode 100644 index 000000000..cfa78b45b --- /dev/null +++ b/integration-tests/src/test/resources/configuration/dataFilePrefix.yml @@ -0,0 +1 @@ +data-file-prefix: database_ \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/dataFileSuffix.ini b/integration-tests/src/test/resources/configuration/dataFileSuffix.ini new file mode 100644 index 000000000..b921c5a69 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/dataFileSuffix.ini @@ -0,0 +1 @@ +data-file-suffix = .some_suffix \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/dataFileSuffix.xml b/integration-tests/src/test/resources/configuration/dataFileSuffix.xml new file mode 100644 index 000000000..edc40cb6b --- /dev/null +++ b/integration-tests/src/test/resources/configuration/dataFileSuffix.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/transactionFilePrefix.ini b/integration-tests/src/test/resources/configuration/transactionFilePrefix.ini new file mode 100644 index 000000000..e6aed7b4f --- /dev/null +++ b/integration-tests/src/test/resources/configuration/transactionFilePrefix.ini @@ -0,0 +1 @@ +transaction-file-prefix = supersupertransacation_ \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/transactionFilePrefix.xml b/integration-tests/src/test/resources/configuration/transactionFilePrefix.xml new file mode 100644 index 000000000..5a3fdaae8 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/transactionFilePrefix.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/transactionFileSuffix.ini b/integration-tests/src/test/resources/configuration/transactionFileSuffix.ini new file mode 100644 index 000000000..9a9e9dfd1 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/transactionFileSuffix.ini @@ -0,0 +1 @@ +transaction-file-suffix = suffix_transaction \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/transactionFileSuffix.xml b/integration-tests/src/test/resources/configuration/transactionFileSuffix.xml new file mode 100644 index 000000000..709a4dd99 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/transactionFileSuffix.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/typeDictionaryFilename.ini b/integration-tests/src/test/resources/configuration/typeDictionaryFilename.ini new file mode 100644 index 000000000..0201c046b --- /dev/null +++ b/integration-tests/src/test/resources/configuration/typeDictionaryFilename.ini @@ -0,0 +1 @@ +type-dictionary-file-name = somesupertypeDictionaryFilename.some_suffix \ No newline at end of file diff --git a/integration-tests/src/test/resources/configuration/typeDictionaryFilename.xml b/integration-tests/src/test/resources/configuration/typeDictionaryFilename.xml new file mode 100644 index 000000000..482b233d0 --- /dev/null +++ b/integration-tests/src/test/resources/configuration/typeDictionaryFilename.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-tests/src/test/resources/junit-platform.properties b/integration-tests/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..b776c3897 --- /dev/null +++ b/integration-tests/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.execution.parallel.enabled = false +junit.jupiter.execution.parallel.mode.default = concurrent diff --git a/integration-tests/src/test/resources/legacy/add.csv b/integration-tests/src/test/resources/legacy/add.csv new file mode 100644 index 000000000..22aff6f15 --- /dev/null +++ b/integration-tests/src/test/resources/legacy/add.csv @@ -0,0 +1,2 @@ +old current +test.eclipse.store.legacy.csv.data.DeletePerson2 test.eclipse.store.legacy.csv.data.DeletePerson \ No newline at end of file diff --git a/integration-tests/src/test/resources/legacy/delete.csv b/integration-tests/src/test/resources/legacy/delete.csv new file mode 100644 index 000000000..ef8b2b933 --- /dev/null +++ b/integration-tests/src/test/resources/legacy/delete.csv @@ -0,0 +1,2 @@ +old current +test.eclipse.store.legacy.csv.data.DeletePerson test.eclipse.store.legacy.csv.data.DeletePerson2 \ No newline at end of file diff --git a/integration-tests/src/test/resources/legacy/refactoring.csv b/integration-tests/src/test/resources/legacy/refactoring.csv new file mode 100644 index 000000000..e57c7f3fa --- /dev/null +++ b/integration-tests/src/test/resources/legacy/refactoring.csv @@ -0,0 +1,3 @@ +old current +test.eclipse.store.legacy.csv.data.CsvPerson test.eclipse.store.legacy.csv.data.CsvPerson2 +test.eclipse.store.legacy.csv.data.CsvPerson#original test.eclipse.store.legacy.csv.data.CsvPerson2#copy \ No newline at end of file diff --git a/integration-tests/src/test/resources/simplelogger.properties b/integration-tests/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..81186aa73 --- /dev/null +++ b/integration-tests/src/test/resources/simplelogger.properties @@ -0,0 +1,38 @@ +# SLF4J's SimpleLogger configuration file + # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + + # Default logging detail level for all instances of SimpleLogger. + # Must be one of ("trace", "debug", "info", "warn", or "error"). + # If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=warn + +org.slf4j.simpleLogger.log.org.eclipse.serializer.nativememory=trace + +org.slf4j.simpleLogger.log.org.eclipse.serializer.monitoring.MonitoringManager=warn + + # Logging detail level for a SimpleLogger instance named "xxxxx". + # Must be one of ("trace", "debug", "info", "warn", or "error"). + # If not specified, the default logging detail level is used. + #org.slf4j.simpleLogger.log.xxxxx= + + # Set to true if you want the current date and time to be included in output messages. + # Default is false, and will output the number of milliseconds elapsed since startup. +org.slf4j.simpleLogger.showDateTime=true + + # The date and time format to be used in the output messages. + # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. + # If the format is not specified or is invalid, the default format is used. + # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z + + # Set to true if you want to output the current thread name. + # Defaults to true. +org.slf4j.simpleLogger.showThreadName=true + + # Set to true if you want the Logger instance name to be included in output messages. + # Defaults to true. +org.slf4j.simpleLogger.showLogName=true + + # Set to true if you want the last component of the name to be included in output messages. + # Defaults to false. +org.slf4j.simpleLogger.showShortLogName=true diff --git a/pom.xml b/pom.xml index 935f8e52c..5aed247ea 100644 --- a/pom.xml +++ b/pom.xml @@ -389,6 +389,12 @@ examples + + IT + + integration-tests + + deploy