From c45a59de3d17c9f02bdbd87c4b6eac0605065b3c Mon Sep 17 00:00:00 2001 From: sttk Date: Sat, 11 Jan 2025 10:13:49 +0900 Subject: [PATCH 1/2] new!: complete revision. names and structure of program items was changed --- .github/workflows/java-ci.yml | 2 +- .github/workflows/javadoc.yml | 2 +- .github/workflows/publish.yml | 2 +- .gitignore | 3 - pom.xml | 177 +- .../java/com/github/sttk/sabi/AsyncGroup.java | 29 +- .../java/com/github/sttk/sabi/DataAcc.java | 11 + .../java/com/github/sttk/sabi/DataConn.java | 17 + .../java/com/github/sttk/sabi/DataHub.java | 87 + .../java/com/github/sttk/sabi/DataSrc.java | 13 + src/main/java/com/github/sttk/sabi/Dax.java | 30 - .../java/com/github/sttk/sabi/DaxBase.java | 418 ---- .../java/com/github/sttk/sabi/DaxConn.java | 67 - .../java/com/github/sttk/sabi/DaxSrc.java | 46 - src/main/java/com/github/sttk/sabi/Logic.java | 25 +- src/main/java/com/github/sttk/sabi/Para.java | 101 - .../java/com/github/sttk/sabi/Runner.java | 12 +- src/main/java/com/github/sttk/sabi/Sabi.java | 142 +- src/main/java/com/github/sttk/sabi/Seq.java | 47 - .../sttk/sabi/async/AsyncGroupAsync.java | 96 - .../sttk/sabi/async/AsyncGroupSync.java | 47 - .../java/com/github/sttk/sabi/errs/Err.java | 258 -- .../com/github/sttk/sabi/errs/ErrHandler.java | 23 - .../com/github/sttk/sabi/errs/ErrOcc.java | 68 - .../sttk/sabi/errs/notify/ErrNotifier.java | 114 - .../github/sttk/sabi/errs/package-info.java | 6 - .../sttk/sabi/internal/AsyncGroupImpl.java | 108 + .../sttk/sabi/internal/DataConnContainer.java | 19 + .../sttk/sabi/internal/DataConnList.java | 38 + .../sttk/sabi/internal/DataHubInner.java | 215 ++ .../sttk/sabi/internal/DataSrcContainer.java | 21 + .../sttk/sabi/internal/DataSrcList.java | 174 ++ src/main/java/module-info.java | 11 +- src/test/java/ReadmeTest.java | 266 -- .../com/github/sttk/sabi/DaxBaseTest.java | 1014 -------- .../java/com/github/sttk/sabi/ParaTest.java | 79 - .../java/com/github/sttk/sabi/SabiTest.java | 390 --- .../java/com/github/sttk/sabi/SeqTest.java | 45 - .../sttk/sabi/async/AsyncGroupAsyncTest.java | 134 - .../sttk/sabi/async/AsyncGroupSyncTest.java | 76 - .../github/sttk/sabi/errs/ErrHandlerTest.java | 79 - .../com/github/sttk/sabi/errs/ErrTest.java | 109 - .../sabi/errs/notify/ErrNotifierTest.java | 351 --- .../sabi/internal/AsyncGroupImplTest.java | 155 ++ .../sttk/sabi/internal/DataAccTest.java | 649 +++++ .../sttk/sabi/internal/DataConnListTest.java | 88 + .../sttk/sabi/internal/DataHubInnerTest.java | 2173 +++++++++++++++++ .../sttk/sabi/internal/DataSrcListTest.java | 972 ++++++++ 48 files changed, 4911 insertions(+), 4098 deletions(-) create mode 100644 src/main/java/com/github/sttk/sabi/DataAcc.java create mode 100644 src/main/java/com/github/sttk/sabi/DataConn.java create mode 100644 src/main/java/com/github/sttk/sabi/DataHub.java create mode 100644 src/main/java/com/github/sttk/sabi/DataSrc.java delete mode 100644 src/main/java/com/github/sttk/sabi/Dax.java delete mode 100644 src/main/java/com/github/sttk/sabi/DaxBase.java delete mode 100644 src/main/java/com/github/sttk/sabi/DaxConn.java delete mode 100644 src/main/java/com/github/sttk/sabi/DaxSrc.java delete mode 100644 src/main/java/com/github/sttk/sabi/Para.java delete mode 100644 src/main/java/com/github/sttk/sabi/Seq.java delete mode 100644 src/main/java/com/github/sttk/sabi/async/AsyncGroupAsync.java delete mode 100644 src/main/java/com/github/sttk/sabi/async/AsyncGroupSync.java delete mode 100644 src/main/java/com/github/sttk/sabi/errs/Err.java delete mode 100644 src/main/java/com/github/sttk/sabi/errs/ErrHandler.java delete mode 100644 src/main/java/com/github/sttk/sabi/errs/ErrOcc.java delete mode 100644 src/main/java/com/github/sttk/sabi/errs/notify/ErrNotifier.java delete mode 100644 src/main/java/com/github/sttk/sabi/errs/package-info.java create mode 100644 src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java create mode 100644 src/main/java/com/github/sttk/sabi/internal/DataConnContainer.java create mode 100644 src/main/java/com/github/sttk/sabi/internal/DataConnList.java create mode 100644 src/main/java/com/github/sttk/sabi/internal/DataHubInner.java create mode 100644 src/main/java/com/github/sttk/sabi/internal/DataSrcContainer.java create mode 100644 src/main/java/com/github/sttk/sabi/internal/DataSrcList.java delete mode 100644 src/test/java/ReadmeTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/DaxBaseTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/ParaTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/SabiTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/SeqTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/async/AsyncGroupAsyncTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/async/AsyncGroupSyncTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/errs/ErrHandlerTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/errs/ErrTest.java delete mode 100644 src/test/java/com/github/sttk/sabi/errs/notify/ErrNotifierTest.java create mode 100644 src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java create mode 100644 src/test/java/com/github/sttk/sabi/internal/DataAccTest.java create mode 100644 src/test/java/com/github/sttk/sabi/internal/DataConnListTest.java create mode 100644 src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java create mode 100644 src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java diff --git a/.github/workflows/java-ci.yml b/.github/workflows/java-ci.yml index c083a03..03f7ebc 100644 --- a/.github/workflows/java-ci.yml +++ b/.github/workflows/java-ci.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - javaver: [21] + javaver: [21, 22, 23] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml index ddb0184..29fa366 100644 --- a/.github/workflows/javadoc.yml +++ b/.github/workflows/javadoc.yml @@ -20,7 +20,7 @@ jobs: - name: Javadoc run: mvn javadoc:javadoc - name: Publish Documentation on GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/site/apidocs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7ba6dae..26fafe9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ jobs: packages: write steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 21 diff --git a/.gitignore b/.gitignore index 0f7e018..45e5f9f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,3 @@ test-results-native/ # OS generating files .DS_Store Thumbs.db - -# GraalVM native build configuration files -src/main/resources/META-INF/native-image diff --git a/pom.xml b/pom.xml index d2cd5db..5f8b3e1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,39 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.github.sttk + io.github.sttk sabi 0.4.0 jar + sabi + A small framework to separate logics and data accesses for Java application. + https://github.com/sttk/sabi-java + + + + The MIT License + https://opensource.org/license/mit/ + repo + + + + + + sttk + Takayuki Sato + https://github.com/sttk + + + + + https://github.com/sttk/sabi-java + scm:git:git://github.com/sttk/sabi-java.git + scm:git:git//github.com/sttk/sabi-java.git + + - 21 - 21 + 21 UTF-8 -J--add-opens=java.base/java.lang=ALL-UNNAMED - --initialize-at-build-time=org.junit.platform.launcher.core.LauncherConfig - --initialize-at-build-time=org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter @@ -148,17 +189,77 @@ trace - -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image + -agentlib:native-image-agent=config-output-dir=target/native-trace/ + + release-gh + + + github + GitHub Packages + https://maven.pkg.github.com/sttk/cliargs-java + + + + + release-ossrh + + + ossrh + Central Repository OSSRH (Snapshot) + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + Central Repository OSSRH + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + + deploy + + deploy + + + + + ossrh + https://s01.oss.sonatype.org + true + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + + - - - github - GitHub Packages - https://maven.pkg.github.com/sttk/sabi-java - - - diff --git a/src/main/java/com/github/sttk/sabi/AsyncGroup.java b/src/main/java/com/github/sttk/sabi/AsyncGroup.java index 1fa8ac0..716ac6c 100644 --- a/src/main/java/com/github/sttk/sabi/AsyncGroup.java +++ b/src/main/java/com/github/sttk/sabi/AsyncGroup.java @@ -1,40 +1,17 @@ /* * AsyncGroup class. - * Copyright (C) 2023 Takayuki Sato. All Rights Reserved. + * Copyright (C) 2023-2025 Takayuki Sato. All Rights Reserved. */ package com.github.sttk.sabi; -import com.github.sttk.sabi.errs.Err; +import com.github.sttk.errs.Exc; import java.util.Map; import java.util.HashMap; -/** - * {@code AsyncGroup} is the inferface to execute added {@link Runner}(s) - * asynchronously. - *

- * The method Add is to add target {@link Runner}(s). - * This interface is used as an argument of {@link DaxSrc#setup}, {@link - * DaxConn#commit}, and {@link DaxConn#rollback}. - */ public interface AsyncGroup { - - /** - * {@code RunnerFailed} is an error reason which indicates that a {@link - * Runner} failed. - */ record RunnerFailed() {} - - /** - * {@code RunnerInterrupted} is an error reason which indicates that a {@link - * Runner}'s thread is interrupted. - */ record RunnerInterrupted() {} - /** - * Adds a runner to be run asynchronously. - * - * @param runner A {@link Runner} object. - */ - void add(Runner runner); + void add(final Runner runner); } diff --git a/src/main/java/com/github/sttk/sabi/DataAcc.java b/src/main/java/com/github/sttk/sabi/DataAcc.java new file mode 100644 index 0000000..a2817ea --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/DataAcc.java @@ -0,0 +1,11 @@ +/* + * DataAcc class. + * Copyright (C) 2023-2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.errs.Exc; + +public interface DataAcc { + C getDataConn(String name, Class cls) throws Exc; +} diff --git a/src/main/java/com/github/sttk/sabi/DataConn.java b/src/main/java/com/github/sttk/sabi/DataConn.java new file mode 100644 index 0000000..146ac85 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/DataConn.java @@ -0,0 +1,17 @@ +/* + * DataConn class. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.errs.Exc; + +public interface DataConn { + void commit(AsyncGroup ag) throws Exc; + default void preCommit(AsyncGroup ag) throws Exc {} + default void postCommit(AsyncGroup ag) {} + default boolean shouldForceBack() { return false; } + void rollback(AsyncGroup ag); + default void forceBack(AsyncGroup ag) {} + void close(); +} diff --git a/src/main/java/com/github/sttk/sabi/DataHub.java b/src/main/java/com/github/sttk/sabi/DataHub.java new file mode 100644 index 0000000..24bfa86 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/DataHub.java @@ -0,0 +1,87 @@ +/* + * DataHub class. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.sabi.internal.DataHubInner; +import com.github.sttk.errs.Exc; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DataHub implements DataAcc, AutoCloseable { + public record FailToSetupGlobalDataSrcs(Map errors) {} + public record FailToSetupLocalDataSrcs(Map errors) {} + public record FailToCommitDataConn(Map errors) {} + public record FailToPreCommitDataConn(Map errors) {} + public record NoDataSrcToCreateDataConn(String name, String dataConnType) {} + public record FailToCreateDataConn(String name, String dataConnType) {} + public record CreatedDataConnIsNull(String name, String dataConnType) {} + public record FailToCastDataConn(String name, String castToType) {} + public record FailToCastDataHub(String castFromType) {} + public record RuntimeExceptionOccured() {} + + private final DataHubInner inner = new DataHubInner(); + + public DataHub() {} + + public void uses(String name, DataSrc ds) { + inner.uses(name, ds); + } + + public void disuses(String name) { + inner.disuses(name); + } + + public void run(Logic logic) throws Exc { + D data; + try { + @SuppressWarnings("unchecked") + D d = (D) this; + data = d; + } catch (Exception e) { + throw new Exc(new FailToCastDataHub(this.getClass().getName())); + } + try { + inner.begin(); + logic.run(data); + } catch (Exc | RuntimeException e) { + throw e; + } finally { + inner.end(); + } + } + + public void txn(Logic logic) throws Exc { + D data; + try { + @SuppressWarnings("unchecked") + D d = (D) this; + data = d; + } catch (Exception e) { + throw new Exc(new FailToCastDataHub(this.getClass().getName())); + } + try { + inner.begin(); + logic.run(data); + inner.commit(); + inner.postCommit(); + } catch (Exc | RuntimeException | Error e) { + inner.rollback(); + throw e; + } finally { + inner.end(); + } + } + + @Override + public C getDataConn(String name, Class cls) throws Exc { + return inner.getDataConn(name, cls); + } + + @Override + public void close() { + inner.close(); + } +} diff --git a/src/main/java/com/github/sttk/sabi/DataSrc.java b/src/main/java/com/github/sttk/sabi/DataSrc.java new file mode 100644 index 0000000..5f78b48 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/DataSrc.java @@ -0,0 +1,13 @@ +/* + * DataSrc class. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.errs.Exc; + +public interface DataSrc { + void setup(AsyncGroup ag) throws Exc; + void close(); + DataConn createDataConn() throws Exc; +} diff --git a/src/main/java/com/github/sttk/sabi/Dax.java b/src/main/java/com/github/sttk/sabi/Dax.java deleted file mode 100644 index ae0ab22..0000000 --- a/src/main/java/com/github/sttk/sabi/Dax.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Dax class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi; - -import com.github.sttk.sabi.errs.Err; - -/** - * {@code Dax} is the interface for a set of data access methods. - * - * This interface is inherited by dax implementations for data stores, and - * each dax implementation defines data access methods to each data store. - * In data access methods, {@link DaxConn} instances conected to data stores - * can be obtained with {@link #getDaxConn} method. - */ -public interface Dax { - - /** - * Gets a {@link DaxConn} instance associated with the argument name. - * The name is same as what was registered with {@link DaxSrc} using - * {@link Sabi#uses} method. - * - * @param The type of a {@link DaxConn}. - * @param name A name of a {@link DaxConn}. - * @return A {@link DaxConn} instance. - * @throws Err If getting a {@link DaxConn} fails. - */ - C getDaxConn(String name) throws Err; -} diff --git a/src/main/java/com/github/sttk/sabi/DaxBase.java b/src/main/java/com/github/sttk/sabi/DaxBase.java deleted file mode 100644 index 8eb3fa2..0000000 --- a/src/main/java/com/github/sttk/sabi/DaxBase.java +++ /dev/null @@ -1,418 +0,0 @@ -/* - * DaxBase class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi; - -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.async.AsyncGroupAsync; -import com.github.sttk.sabi.async.AsyncGroupSync; - -/** - * {@code DaxBase} is the class that defines the methods to manage - * {@link DaxSrc}(s). - * And this class defines private methods to process a transaction. - */ -public class DaxBase implements Dax, AutoCloseable { - /** - * {@code FailToSetupGlobalDaxSrcs} is the error reason which indicates that - * some {@link DaxSrc}(s) failed to set up. - * - * @param errors The map holding keys that are the registered names of - * {@link DaxSrc}(s) failed, and values that are {@link Err}(s) having - * their error reasons. - */ - public record FailToSetupGlobalDaxSrcs(Map errors) {}; - - /** - * {@code FailToSetupLocalDaxSrc} is the error reason which indicates a local - * {@link DaxSrc} failed to set up. - * - * @param name Tthe registered name of the {@link DaxSrc} failed. - */ - public record FailToSetupLocalDaxSrc(String name) {}; - - /** - * {@code DaxSrcIsNotFound} is the error reason which indicates that a - * specified {@link DaxSrc} is not found. - * - * @param name The registered name of the {@link DaxSrc} not found. - */ - public record DaxSrcIsNotFound(String name) {} - - /** - * {@code FailToCreateDaxConn} is the error reason which indicates that it is - * failed to create a new connection to a data store. - * - * @param name The registered name of the {@link DaxSrc} failed to create a - * {@link DaxConn}. - */ - public record FailToCreateDaxConn(String name) {} - - /** - * {@code FailToCommitDaxConn} is the error reason which indicates that some - * connections failed to commit. - * - * @param errors The map holding keys that are the names of - * {@link DaxConn}(s) failed, and values that are {@link Err}(s) having - * their error reasons. - */ - public record FailToCommitDaxConn(Map errors) {} - - /** - * {@code CreatedDaxConnIsNull} is the error reason which indicates that a - * {@link DaxSrc} created a {@link DaxConn} interface but it is null. - * - * @param name The name of the {@link DaxSrc} that try to create a - * {@link DaxConn}. - */ - public record CreatedDaxConnIsNull(String name) {} - - /** - * {@code FailToRunLogic} is the error reason which indicates that a logic - * failed to run. - * - * @param logicType The logic class failed. - */ - public record FailToRunLogic(Class logicType) {} - - /** The flag to prevent further registration of global {@link DaxSrc}(s). */ - private static boolean isGlobalDaxSrcsFixed; - - /** The map for registering global {@link DaxSrc} instances. */ - private static final Map globalDaxSrcMap; - - static { - globalDaxSrcMap = new LinkedHashMap<>(); - } - - /** - * Registers to enable data accesses to data store associated with the - * argument {@link DaxSrc} in all dax instances. - */ - static void addGlobalDaxSrc(String name, DaxSrc ds) { - if (isGlobalDaxSrcsFixed) { - return; - } - globalDaxSrcMap.putIfAbsent(name, ds); - } - - /** - * Makes all globally registered {@link DaxSrc}(s) usable. - * This method forbids adding more global {@link DaxSrc}(s), and call each - * {@link DaxSrc#setup} method of all registered {@link DaxSrc}(s). - * - * If one of {@link DaxSrc}(s) fails to execute synchronous {@link - * DaxSrc#setup}, this method stops other setting up and throws an {@link - * Err} containing the error reason of that failure. - * - * If one of {@link DaxSrc}(s) fails to execute asynchronous {@link - * DaxSrc#setup}, this function continue to other setting up and throws - * an {@link Err} containing the error reason of that failure and other - * errors if any. - * - * @throws Err If an exception occuers by either of the following reasons: - *

- */ - static void setupGlobalDaxSrcs() throws Err { - isGlobalDaxSrcsFixed = true; - Err.fixCfg(); - - var ag = new AsyncGroupAsync(); - - for (var ent : globalDaxSrcMap.entrySet()) { - try { - ag.name = ent.getKey(); - ent.getValue().setup(ag); - } catch (Exception e) { - ag.join(); - ag.addErr(ag.name, e); - throw new Err(new FailToSetupGlobalDaxSrcs(ag.makeErrs())); - } - } - - ag.join(); - - if (ag.hasErr()) { - throw new Err(new FailToSetupGlobalDaxSrcs(ag.makeErrs())); - } - } - - /** - * Closes and frees each resource of registered global {@link DaxSrc}(s). - * This method should always be called before an application ends. - */ - static void closeGlobalDaxSrcs() { - for (var ent : globalDaxSrcMap.entrySet()) { - ent.getValue().close(); - } - } - - /** The flag to prevent further registration of local {@link DaxSrc}(s). */ - private boolean isLocalDaxSrcsFixed; - - /** The map for registering local {@link DaxSrc} instances. */ - private Map localDaxSrcMap = new LinkedHashMap<>(); - - /** The map for registering {@link DaxConn} instances. */ - private Map daxConnMap = new LinkedHashMap<>(); - - /** - * The lock to registering {@link DaxConn} exclusively for this - * {@link DaxBase} instance. - */ - private final Lock daxConnLock = new ReentrantLock(); - - /** - * The default constructor. - */ - public DaxBase() { - isGlobalDaxSrcsFixed = true; - Err.fixCfg(); - } - - /** - * Closes and frees all local {@link DaxSrc}(s). - */ - @Override - public void close() { - if (isLocalDaxSrcsFixed) { - return; - } - - for (var ds : localDaxSrcMap.values()) { - ds.close(); - } - localDaxSrcMap.clear(); - } - - /** - * Registers and sets up a local {@link DaxSrc} with an argument name. - * - * @param name The name for the {@link DaxSrc} to be registered. - * @param ds The {@link DaxSrc} instance to be registered. - * @throws Err If an exception occuers by either of the following reasons: - *
    - *
  • {@link com.github.sttk.sabi.DaxBase.FailToSetupLocalDaxSrc} - * If failing to setup some of local {@link DaxSrc}(s).
  • - *
- */ - public void uses(String name, DaxSrc ds) throws Err { - if (isLocalDaxSrcsFixed) { - return; - } - - var agSync = new AsyncGroupSync(); - - try { - ds.setup(agSync); - } catch (Exception e) { - throw new Err(new FailToSetupLocalDaxSrc(name), e); - } - - if (agSync.getErr() != null) { - throw new Err(new FailToSetupLocalDaxSrc(name), agSync.getErr()); - } - - localDaxSrcMap.putIfAbsent(name, ds); - } - - /** - * Closes and removes a local {@link DaxSrc} specified by the argument name. - * - * @param name The name of the local {@link DaxSrc} to be removed. - */ - public void disuses(String name) { - if (isLocalDaxSrcsFixed) { - return; - } - - var ds = localDaxSrcMap.remove(name); - if (ds != null) { - ds.close(); - } - } - - /** - * Begins a transaction processing. - * This method forbids registration of more local {@link DaxSrc}(s) while - * a transaction processing. - */ - protected void begin() { - isLocalDaxSrcsFixed = true; - } - - /** - * Commits updates in a transaction processing. - * - * @throws Err If an exception occuers by either of the following reasons: - *
    - *
  • {@link com.github.sttk.sabi.DaxBase.FailToCommitDaxConn} - * If failing to commit updates via some {@link DaxConn}(s).
  • - *
- */ - protected void commit() throws Err { - var ag = new AsyncGroupAsync(); - - for (var ent : daxConnMap.entrySet()) { - ag.name = ent.getKey(); - try { - ent.getValue().commit(ag); - } catch (Exception e) { - ag.join(); - ag.addErr(ent.getKey(), e); - throw new Err(new FailToCommitDaxConn(ag.makeErrs())); - } - } - - ag.join(); - - if (ag.hasErr()) { - throw new Err(new FailToCommitDaxConn(ag.makeErrs())); - } - } - - /** - * Rollbacks all updates in a transaction. - */ - protected void rollback() { - var ag = new AsyncGroupAsync(); - - for (var ent : daxConnMap.entrySet()) { - var conn = ent.getValue(); - if (conn.isCommitted()) { - conn.forceBack(ag); - } else { - conn.rollback(ag); - } - } - - ag.join(); - } - - /** - * Ends a transaction. - * This method closes all {@link DaxConn} and removes them from this - * {@link DaxBase}.. - */ - protected void end() { - for (var conn : daxConnMap.values()) { - conn.close(); - } - daxConnMap.clear(); - - isLocalDaxSrcsFixed = false; - } - - private class ErrWrapper extends RuntimeException { - Err err; - ErrWrapper(Err err) { - this.err = err; - } - } - - /** - * {@inheritDoc} - */ - public C getDaxConn(String name) throws Err { - var conn = daxConnMap.get(name); - if (conn != null) { - @SuppressWarnings("unchecked") - final C c = (C) conn; - return c; - } - - daxConnLock.lock(); - - try { - @SuppressWarnings("unchecked") - final C c = (C) daxConnMap.computeIfAbsent(name, key -> { - var ds = localDaxSrcMap.get(key); - if (ds == null) { - ds = globalDaxSrcMap.get(key); - } - if (ds == null) { - var err = new Err(new DaxSrcIsNotFound(key)); - throw new ErrWrapper(err); - } - - DaxConn dc = null; - try { - dc = ds.createDaxConn(); - } catch (Exception e) { - var err = new Err(new FailToCreateDaxConn(key), e); - throw new ErrWrapper(err); - } - if (dc == null) { - var err = new Err(new CreatedDaxConnIsNull(key)); - throw new ErrWrapper(err); - } - return dc; - }); - - return c; - - } catch (ErrWrapper e) { - throw e.err; - - } finally { - daxConnLock.unlock(); - } - } - - /** - * Executes logics in a transaction. - * - * First, this method casts the argument {@link DaxBase} to the type - * specified as a logic's argument. - * Next, this method begins the transaction, and executes the argument - * logics. - * Then, if no error occurs, this method commits all updates in the - * transaction, otherwise rollbaks them. - * If there are commit errors after some {@link DaxConn}(s) are committed, - * or there are {@link DaxConn}(s) which don't have rollback mechanism, - * this method executes {@link DaxConn#forceBack} methods of these - * {@link DaxConn}(s). - * And after that, this method ends the transaction. - * - * During a transaction, it is denied to add or remove any local - * {@link DaxSrc}(s). - */ - @SafeVarargs - public final void txn(Logic ...logics) throws Err { - @SuppressWarnings("unchecked") - final D dax = (D) this; - - begin(); - - try { - for (var logic : logics) { - try { - logic.run(dax); - } catch (Err e) { - throw e; - } catch (Exception e) { - throw new Err(new FailToRunLogic(logic.getClass()), e); - } - } - - commit(); - - } catch (Err | RuntimeException | Error e) { - rollback(); - throw e; - - } finally { - end(); - } - } -} diff --git a/src/main/java/com/github/sttk/sabi/DaxConn.java b/src/main/java/com/github/sttk/sabi/DaxConn.java deleted file mode 100644 index 3c40fe9..0000000 --- a/src/main/java/com/github/sttk/sabi/DaxConn.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * DaxConn class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi; - -import com.github.sttk.sabi.errs.Err; - -/** - * {@code DaxConn} is the interface that represents a connection to a data store. - * This interface declares methods: {@link #commit}, {@link rollback} and - * {@link #close} to - * work in a transaction - * process. - * - * {@link #commit} is the method for comming updates in a transaction. - * {@link #rollback} is the method for rollback updates in a transaction. - * If commiting and rollbacking procedures are asynchronous, the argument - * {@link AsyncGroup}(s) are used to process them. - * {@link #close} is the method to close this connection. - */ -public interface DaxConn { - - /** - * Commits updates to a target data store in a transaction. - * If the commit procedure is asynchronous, it is processed with an argument - * {@link AsyncGroup} object. - * - * @param ag An {@link AsyncGroup} object which is used if this commit - * procedure is asynchronous. - * @throws Err If commiting updates fails. - */ - void commit(AsyncGroup ag) throws Err; - - /** - * Checks whether updates are already committed. - * - * @return true if committed, or no rollback mechanism. - */ - default boolean isCommitted() { - return true; - } - - /** - * Rollbacks updates to a target data store in a transaction. - * If the rollback procedure is asynchronous, it is processed with an - * argument {@link AsyncGroup} object. - * - * @param ag An {@link AsyncGroup} object which is used if this rollback - * procedure is asynchronous. - */ - default void rollback(AsyncGroup ag) {} - - /** - * Reverts updates forcely even if updates are already commited or this - * connection does not have rollback mechanism. - * - * @param ag An {@link AsyncGroup} object which is used if this rollback - * procedure is asynchronous. - */ - void forceBack(AsyncGroup ag); - - /** - * Closes this connection. - */ - void close(); -} diff --git a/src/main/java/com/github/sttk/sabi/DaxSrc.java b/src/main/java/com/github/sttk/sabi/DaxSrc.java deleted file mode 100644 index 5312257..0000000 --- a/src/main/java/com/github/sttk/sabi/DaxSrc.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * DaxSrc class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi; - -import com.github.sttk.sabi.errs.Err; - -/** - * {@code DaxSrc} is the interface that represents a data source like database, - * etc.,and creates a {@link DaxConn} which is a connection to the data source. - * This interface declares three methods: {@link #setup}, {@link #close}, and - * {@link #createDaxConn}. - * - * {@link #setup} is the method to connect to a data store and to prepare to - * create {@link DaxConn} objects which represents a connection to access data - * in the data store. - * If the set up procedure is asynchronous, the {@link #setup} method is - * implemented so as to use {@link AsyncGroup}. - * {@link #close} is the method to disconnect to a data store. - * {@link #createDaxConn} is the method to create a {@link DaxConn} object. - */ -public interface DaxSrc { - /** - * Sets up this data source. - * - * @param ag An {@link AsyncGroup} object which is used if a setting up - * process is asynchronous. - * @throws Err If setting up pprocess is synchronous and fails. - */ - void setup(AsyncGroup ag) throws Err; - - /** - * Closes this data source. - */ - void close(); - - /** - * Creates a {@link DaxConn} object which represents a connection to this - * data source. - * - * @return A {@link DaxConn} object. - * @throws Err If creating a {@link DaxConn} fails. - */ - DaxConn createDaxConn() throws Err; -} diff --git a/src/main/java/com/github/sttk/sabi/Logic.java b/src/main/java/com/github/sttk/sabi/Logic.java index 34386fa..46b0908 100644 --- a/src/main/java/com/github/sttk/sabi/Logic.java +++ b/src/main/java/com/github/sttk/sabi/Logic.java @@ -1,31 +1,12 @@ /* * Logic class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. */ package com.github.sttk.sabi; -import com.github.sttk.sabi.errs.Err; +import com.github.sttk.errs.Exc; -/** - * {@code Logic} is the functional interface that represents a logical procedure. - * The {@link #run} method of the class inheriting this interface implements - * only logic processing. - * Data access processing is described only by calling methods of the argument - * dax, and the details are implemented elsewhere. - * - * @param dax - */ @FunctionalInterface public interface Logic { - - /** - * Runs the logical procedure represented by this class. - * - * This method is the entry point of the whole of this logical prcedure. - * - * @param dax A Dax instance providing data access methods for this logical - * procedure. - * @throws Err If an error occurs within this logic processing. - */ - void run(D dax) throws Err; + void run(D data) throws Exc; } diff --git a/src/main/java/com/github/sttk/sabi/Para.java b/src/main/java/com/github/sttk/sabi/Para.java deleted file mode 100644 index b481022..0000000 --- a/src/main/java/com/github/sttk/sabi/Para.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Para class. - * Copyright (C) 2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi; - -import com.github.sttk.sabi.errs.Err; - -import java.util.Map; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ExecutionException; - -/** - * {@code Para} is a class to runs {@link Runner}(s) in parallel. - */ -public class Para implements Runner { - - /** The array of {@link Runner}(s). */ - private final Runner[] runners; - - /** - * An error reason which indicates some runner which is runned in parallel - * failed. - * - * @param errors A map contains {@link Err} objects with parallelized - * runner's indexes. - */ - public record FailToRunInParallel(Map errors) {}; - - /** - * An error reason which indicates that an exception occurs in parallelized - * runner. - */ - public record RunInParallelExceptionOccurs() {}; - - /** - * The constructor which takes an array of {@link Runner}(s) as arguments. - * - * @param runners {@link Runner}'s variadic arguments. - */ - public Para(Runner ...runners) { - this.runners = runners; - } - - /** - * Runs the {@link Runner}(s) holding in this object in parallel. - * - * @throws Err If it is failed to run some of the runners. - */ - @Override - public void run() throws Err { - Para.run(runners); - } - - /** - * Runs the specified {@link Runner}(s) in parallel. - * - * @param runners {@link Runner}'s variadic arguments. - * @throws Err If it is failed to run some of the runners. - */ - public static void run(final Runner... runners) throws Err { - final var factory = Thread.ofVirtual().factory(); - final var executors = Executors.newFixedThreadPool(runners.length, factory); - final var futures = new ArrayList(runners.length); - final var errors = new HashMap(); - try { - for (var runner : runners) { - futures.add(executors.submit(() -> { - runner.run(); - return null; - })); - } - - final var it = futures.iterator(); - for (int i = 0; it.hasNext(); i++) { - try { - it.next().get(); - } catch (ExecutionException exc) { - var cause = exc.getCause(); - if (cause instanceof Err) { - errors.put(i, Err.class.cast(cause)); - } else { - errors.put(i, new Err(new RunInParallelExceptionOccurs(), cause)); - } - } catch (Exception e) { - errors.put(i, new Err(new RunInParallelExceptionOccurs(), e)); - } - } - } finally { - executors.shutdown(); - } - - if (!errors.isEmpty()) { - throw new Err(new FailToRunInParallel(errors)); - } - } -} diff --git a/src/main/java/com/github/sttk/sabi/Runner.java b/src/main/java/com/github/sttk/sabi/Runner.java index a21cec1..99df6cf 100644 --- a/src/main/java/com/github/sttk/sabi/Runner.java +++ b/src/main/java/com/github/sttk/sabi/Runner.java @@ -1,13 +1,13 @@ /* * Runner class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. */ package com.github.sttk.sabi; -import com.github.sttk.sabi.errs.Err; +import com.github.sttk.errs.Exc; /** - * {@code Runner} is the interface that runs any procedure.. + * {@code Runner} is the interface that runs any procedure. */ @FunctionalInterface public interface Runner { @@ -15,9 +15,9 @@ public interface Runner { /** * Runs the procedure that this instance represents. * This method takes no argument and returns nothing. - * And this method throws an {@link Err} exception if this method failed. + * And this method throws an {@link Exc} exception if this method failed. * - * @throws Err If this method failed. + * @throws Exc If this method failed. */ - void run() throws Err; + void run() throws Exc; } diff --git a/src/main/java/com/github/sttk/sabi/Sabi.java b/src/main/java/com/github/sttk/sabi/Sabi.java index 52a25bb..e732a03 100644 --- a/src/main/java/com/github/sttk/sabi/Sabi.java +++ b/src/main/java/com/github/sttk/sabi/Sabi.java @@ -1,146 +1,24 @@ /* * Sabi class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. */ package com.github.sttk.sabi; -import com.github.sttk.sabi.errs.Err; +import com.github.sttk.sabi.internal.DataHubInner; +import com.github.sttk.errs.Exc; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicBoolean; -/** - * {@code Sabi} is the class that provies the static methods related to the - * global fanctionalities of sabi framework. - *

- * This class declares {@link #uses uses} method to register a {@link DaxSrc} - * object used globally with its name. - * And this class also declares {@link #setup setup}, {@link #close close}, - * and {@link #startApp startApp} methods. - * {@link #setup setup} is the static method to setup all global registered - * {@link DaxSrc} objects, and {@link #close close} is the static method to - * free each resource of global {@link DaxSrc} objects. - * {@link #startApp startApp} is the method that executes {@link #setup setup} - * and return {@link AutoCloseable} object which executes the {@link #close - * close} method in its {@link AutoCloseable#close} method. - *

- * The usages of these static methods is as follows: - * - *

   public class Application {
- *       static {
- *           Sabi.uses("foo", new FooDaxSrc());
- *           Sabi.uses("bar", new BarDaxSrc());
- *       }
- *       public static void main(String ...args) {
- *           int exitCode = 0;
- *           try {
- *               Sabi.setup();
- *               ...
- *           } catch (Exception e) {
- *               exitCode = 1;
- *           } finally {
- *               Sabi.close();
- *           }
- *           System.exit(exitCode);
- *       }
- *   }
- * - * Or, - * - *
   public class Application {
- *       static {
- *           Sabi.uses("foo", new FooDaxSrc());
- *           Sabi.uses("bar", new BarDaxSrc());
- *       }
- *       public static void main(String ...args) {
- *           try (var ac = Sabi.startApp()) {
- *               ...
- *           } catch (Exception e) {
- *               System.exit(1);
- *           }
- *       }
- *   }
- */ public final class Sabi { - - /** - * The default constructor. - */ private Sabi() {} - /** - * Registers a global {@link DaxSrc} object with its name to enable to use - * {@link DaxConn} created by the argument {@link DaxSrc} in all - * transactions. - *

- * If a {@link DaxSrc} is tried to register with a name already registered, - * it is ignored and a {@link DaxSrc} registered with the same name first is - * used. - * And this method ignore adding {@link DaxSrc}(s) after {@link #setup setup} - * or beginning of {@link DaxBase#txn}. - * - * @param name The name for the argument {@link DaxSrc} object. - * @param ds A {@link DaxSrc} object. - */ - public static void uses(String name, DaxSrc ds) { - DaxBase.addGlobalDaxSrc(name, ds); - } - - /** - * Makes the globally registered {@link DaxSrc} object usable. - *

- * This method forbids adding more global {@link DaxSrc}, and called each - * {@link DaxSrc#setup setup} method of all registered {@link DaxSrc} - * objects. - * If one of global {@link DaxSrc} objects fails to execute synchronous - * {@link DaxSrc#setup setup}, this function stops other setting up and - * returns an {@link Err} containing the error reason of that failure. - *

- * If one of global {@link DaxSrc} objects fails to execute asynchronous - * {@link DaxSrc#setup setup}, this function continue to other setting up - * and returns an {@link Err} containing the error reason of that failure - * and other errors if any. - * - * @throws Err If one of global {@link DaxSrc} objects. - */ - public static void setup() throws Err { - DaxBase.setupGlobalDaxSrcs(); - } - - /** - * Closes and frees each resource of registered global {@link DaxSrc} - * objects. - */ - public static void close() { - DaxBase.closeGlobalDaxSrcs(); + public static void uses(String name, DataSrc ds) { + DataHubInner.usesGlobal(name, ds); } - /** - * Executes {@link #setup setup} method, and returns an {@link AutoCloseable} - * object of which {@link AutoCloseable#close close} method executes {@link - * #close close} method of this class in it. - * - * @return An {@link AutoCloseable} object. - * @throws Err If one of global {@link DaxSrc} objects. - */ - public static AutoCloseable startApp() throws Err { - try { - DaxBase.setupGlobalDaxSrcs(); - } catch (Err | RuntimeException | Error e) { - DaxBase.closeGlobalDaxSrcs(); - throw e; - } - - return new Closer(); + public static AutoCloseable setup() throws Exc { + return DataHubInner.setupGlobals(); } } -/** - * Closer is the private class. - */ -class Closer implements AutoCloseable { - /** - * {@inheritDoc} - */ - @Override - public void close() { - DaxBase.closeGlobalDaxSrcs(); - } -} diff --git a/src/main/java/com/github/sttk/sabi/Seq.java b/src/main/java/com/github/sttk/sabi/Seq.java deleted file mode 100644 index 970c1d6..0000000 --- a/src/main/java/com/github/sttk/sabi/Seq.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Seq class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi; - -import com.github.sttk.sabi.errs.Err; - -/** - * {@code Seq} is a class to run {@link Runner}(s) sequencially. - */ -public class Seq implements Runner { - - /* The array of {@link Runner}(s). */ - private final Runner[] runners; - - /** - * The constructor which takes an array of {@link Runner}(s) as arguments. - * - * @param runners {@link Runner}'s variadic arguments. - */ - public Seq(Runner ...runners) { - this.runners = runners; - } - - /** - * Runs the {@link Runner}(s) holding in this object sequencially. - * - * @throws Err If it is failed to run one of the runners. - */ - @Override - public void run() throws Err { - run(runners); - } - - /** - * Runs the specified {@link Runner}(s) sequencially. - * - * @param runners {@link Runner}'s variadic arguments. - * @throws Err If it is failed to run one of the runners. - */ - public static void run(final Runner... runners) throws Err { - for (var runner : runners) { - runner.run(); - } - } -} diff --git a/src/main/java/com/github/sttk/sabi/async/AsyncGroupAsync.java b/src/main/java/com/github/sttk/sabi/async/AsyncGroupAsync.java deleted file mode 100644 index 7e14412..0000000 --- a/src/main/java/com/github/sttk/sabi/async/AsyncGroupAsync.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * AsyncGroupAsync class. - * Copyright (C) 2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.async; - -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.Runner; -import com.github.sttk.sabi.errs.Err; - -import java.util.Map; -import java.util.HashMap; - -/** - * {@code AsyncGroupAsync} is the class to run added {@link Runner}(s) on {@link - * AsyncGroup} API asynchronously. - * - * @param The type of a name of a runner or its error. - */ -public class AsyncGroupAsync implements AsyncGroup { - - /** A map that holds {@link Err}(s) by {@link Runner}. */ - private Map errMap = new HashMap<>(); - - /** A map that holds mappings of a name and a thread. */ - private Map vthMap = new HashMap<>(); - - /** A name which will be run. */ - public N name; - - /** - * The default constructor. - */ - public AsyncGroupAsync() {} - - /** - * {@inheritDoc} - */ - @Override - public void add(final Runner runner) { - final var name = this.name; - var vth = Thread.ofVirtual().start(() -> { - try { - runner.run(); - } catch (Err | RuntimeException e) { - addErr(name, e); - } - }); - vthMap.put(name, vth); - } - - /** - * Adds an {@link Exception} by a {@link Runner}. - * - * @param name A name of a {@link Runner}. - * @param exc An {@link Err} by a {@link Runner}. - */ - public synchronized void addErr(N name, Exception exc) { - if (exc instanceof Err) { - errMap.put(name, Err.class.cast(exc)); - } else { - addErr(name, new Err(new RunnerFailed(), exc)); - } - } - - /** - * Checks whether there are errors by {@link Runner}(s). - * - * @return {@code true} if there are errors. - */ - public boolean hasErr() { - return !errMap.isEmpty(); - } - - /** - * Creates a map which holds mappings of a name and an {@link Err}. - * - * @return A map of names and {@link Err}(s). - */ - public Map makeErrs() { - return errMap; - } - - /** - * Waits for completions of {@link Runner}'s threads. - */ - public void join() { - for (var ent : vthMap.entrySet()) { - try { - ent.getValue().join(); - } catch (InterruptedException e) { - addErr(ent.getKey(), new Err(new RunnerInterrupted(), e)); - } - } - } -} diff --git a/src/main/java/com/github/sttk/sabi/async/AsyncGroupSync.java b/src/main/java/com/github/sttk/sabi/async/AsyncGroupSync.java deleted file mode 100644 index 014d9b5..0000000 --- a/src/main/java/com/github/sttk/sabi/async/AsyncGroupSync.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * AsyncGroupSync class. - * Copyright (C) 2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.async; - -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.Runner; -import com.github.sttk.sabi.errs.Err; - -/** - * {@code AsyncGroupSync} is the class to run added {@link Runner}(s) on {@link - * AsyncGroup} API but synchronously. - */ -public class AsyncGroupSync implements AsyncGroup { - - /** An error result when a {@link Runner} object runs. */ - private Err err = null; - - /** - * The default constructor. - */ - public AsyncGroupSync() {} - - /** - * {@inheritDoc} - */ - @Override - public void add(final Runner runner) { - try { - runner.run(); - } catch (Err e) { - this.err = e; - } catch (Exception e) { - this.err = new Err(new RunnerFailed(), e); - } - } - - /** - * Gets a {@link Err} object that this instance holds. - * - * @return A {@link Err} object. - */ - public Err getErr() { - return this.err; - } -} diff --git a/src/main/java/com/github/sttk/sabi/errs/Err.java b/src/main/java/com/github/sttk/sabi/errs/Err.java deleted file mode 100644 index 76defcc..0000000 --- a/src/main/java/com/github/sttk/sabi/errs/Err.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Err class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.errs; - -import java.util.Map; -import java.util.HashMap; -import java.lang.reflect.Field; -import java.util.StringJoiner; - -import com.github.sttk.sabi.errs.notify.ErrNotifier; - -/** - * {@code Err} is the exception class with a reason. - *
- * This class has a record value which indicates a reason by which this - * exception is caused. - * A record as a reason can have some fields that helps to know error situation - * where this exception is caused. - *
- * The example code of creating and throwing an excepton is as follows: - *

{@code
- *   public record FailToDoSomething(String name, int value) {}
- *
- *   throw new Err(new FailToDoSomething("abc", 123));
- * }
- */ -public final class Err extends Exception { - - /** The serial version UID. */ - private static final long serialVersionUID = -4983633951387450536L; - - /** The reason by which this exception is caused. */ - private final Record reason; - - /** The stack trace for the location of occurrence. */ - private final StackTraceElement trace; - - /** The notifier of {@link Err} instance creations. */ - private static final ErrNotifier notifier = new ErrNotifier(); - - /** - * A constructor which constructs a new {@link Err} instance with a specified - * reason. - * A reason is a structore type of which name expresses what is a reason. - * - * @param reason A reason of this exception. - */ - public Err(final Record reason) { - if (reason == null) { - throw new NullPointerException("reason"); - } - this.reason = reason; - - this.trace = getStackTrace()[0]; - - notifier.notify(this); - } - - /** - * A constructor which constructs a new {@link Err} instance with a specified - * reason and an cause. - * A reason is a structore type of which name expresses what is a reason. - * - * @param reason A reason of this exception. - * @param cause A cause exception. - */ - public Err(final Record reason, final Throwable cause) { - super(cause); - - if (reason == null) { - throw new NullPointerException("reason"); - } - this.reason = reason; - - this.trace = getStackTrace()[0]; - } - - /** - * Gets the reason's {@link Record} instance of this exception. - * - * @return The reason of this exception. - */ - public Record getReason() { - return this.reason; - } - - /** - * Gets the simple name of this reason's class. - * - * @return The simple name of this reason's class. - */ - public String getReasonName() { - return this.reason.getClass().getSimpleName(); - } - - /** - * Gets the package full name of this reason's class. - * - * @return The package full name of this reason's class. - */ - public String getReasonPackage() { - return this.reason.getClass().getPackageName(); - } - - /** - * Gets a message expresses the content of this exception. - * - * @return A error message. - */ - @Override - public String getMessage() { - final var sj = new StringJoiner(", ", "{", "}"); - - sj.add("reason=" + this.reason.getClass().getSimpleName()); - - for (Field f : this.reason.getClass().getDeclaredFields()) { - try { - f.setAccessible(true); - sj.add(f.getName() + "=" + f.get(this.reason)); - } catch (Exception e) {} - } - - final Throwable t = getCause(); - if (t != null) { - if (t instanceof Err) { - sj.add("cause=" + Err.class.cast(t).getMessage()); - } else { - sj.add("cause=" + t.toString()); - } - } - - return sj.toString(); - } - - /** - * Gets a field value of the reason object by the specified name. - * If the specified named field is not found in the reason of this {@link Err}, - * this method finds a same named field in reasons of cause exception - * hierarchically. - * - * @param name A field name of the reason object in this exception. - * @return A field value of the reason object by the specified name. - */ - public Object get(final String name) { - try { - final Field f = this.reason.getClass().getDeclaredField(name); - f.setAccessible(true); - return f.get(this.reason); - } catch (NoSuchFieldException e) { - final Throwable t = getCause(); - if (t != null && t instanceof Err) { - return Err.class.cast(t).get(name); - } - } catch (Exception e) {} - - return null; - } - - /** - * Gets a field value of the reason object by the specified {@link Enum} - * name. - * If the specified named field is not found in the reason of this {@link Err}, - * this method finds a same named field in reasons of cause exception - * hierarchically. - * - * @param name An {@link Enum} that same with a field name of the reason - * object in this exception. - * @return A field value of the reason object by the specified {@link Enum} - * name. - */ - public Object get(final Enum name) { - return get(name.name()); - } - - /** - * Gets a map which contains variables that represent error situation. - * - * @return A map of error situation variables. - */ - public Map getSituation() { - final var map = new HashMap(); - for (Field f : this.reason.getClass().getDeclaredFields()) { - try { - f.setAccessible(true); - map.put(f.getName(), f.get(this.reason)); - } catch (Exception e) {} - } - - final Throwable t = getCause(); - if (t != null && t instanceof Err) { - map.putAll(Err.class.cast(t).getSituation()); - } - - return map; - } - - /** - * Gets the name of the source file of this error occurance. - * - * This method can return null if this information is unavailable. - * - * @return The name of the source file of this error occurence. - */ - protected String getFileName() { - return trace.getFileName(); - } - - /** - * Gets the line number in the source file of this error occurence. - * - * This method can return a negative number if this information is - * unavailable. - * - * @return The line number in the source file of this error occurence. - */ - protected int getLineNumber() { - return trace.getLineNumber(); - } - - /** - * Adds an {@link ErrHandler} object which is executed synchronously just - * after an {@link Err} is created. - * - * Handlers added with this method are executed in the order of addition - * and stop if one of the handlers throws a {@link RuntimeException} or - * an {@link Error}. - * - * @param handler An {@link ErrHandler} object. - */ - public static void addSyncHandler(final ErrHandler handler) { - notifier.addSyncHandler(handler); - } - - /** - * Adds an {@link ErrHandler} object which is executed asynchronously just - * after an {@link Err} is created. - * - * Handlers don't stop even if one of the handlers throw a - * {@link RuntimeException} or an {@link Error}. - * - * @param handler An {@link ErrHandler} object. - */ - public static void addAsyncHandler(final ErrHandler handler) { - notifier.addAsyncHandler(handler); - } - - /** - * Fixes configuration of {@link Err}. - * - * After calling this method, any more {@link ErrHandler}s cannot be - * registered, and notification of {@link Err} creations becomes effective. - */ - public static void fixCfg() { - notifier.fix(); - } -} diff --git a/src/main/java/com/github/sttk/sabi/errs/ErrHandler.java b/src/main/java/com/github/sttk/sabi/errs/ErrHandler.java deleted file mode 100644 index 954692c..0000000 --- a/src/main/java/com/github/sttk/sabi/errs/ErrHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * ErrHandler class. - * Copyright (C) 2022 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.errs; - -import java.time.OffsetDateTime; - -/** - * {@code ErrHandler} is a handler of an {@link Err} object creation. - */ -@FunctionalInterface -public interface ErrHandler { - - /** - * Handles an {@link Err} object creation. - * - * @param err An {@link Err} object. - * @param occ An {@link ErrOcc} object which holds the situation parameters - * that indicates when and where an {@link Err} occured. - */ - void handle(Err err, ErrOcc occ); -} diff --git a/src/main/java/com/github/sttk/sabi/errs/ErrOcc.java b/src/main/java/com/github/sttk/sabi/errs/ErrOcc.java deleted file mode 100644 index a122f11..0000000 --- a/src/main/java/com/github/sttk/sabi/errs/ErrOcc.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * ErrOcc class. - * Copyright (C) 2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.errs; - -import java.time.OffsetDateTime; - -/** - * {@code ErrOcc} is the class which contains time and position in a source - * file when and where an {@link Err} occured. - */ -public final class ErrOcc { - - /** Time when an {@link Err} occured. */ - private final OffsetDateTime time; - - /** The source file name where an {@link Err} occured. */ - private final String file; - - /** The line number where an {@link Err} occured. */ - private final int line; - - /** - * The constructor which takes an {@link Err} object as an argument. - * - * @param e An {@link Err} object. - */ - public ErrOcc(final Err e) { - this.time = OffsetDateTime.now(); - this.file = e.getFileName(); - this.line = e.getLineNumber(); - } - - /** - * Gets time when an {@link Err} occured. - * - * @return A {@link OffsetDateTime} object. - */ - public OffsetDateTime getTime() { - return time; - } - - /** - * Gets the source file name where an {@link Err} occured. - * - * This source file name can be null if this is unavailable in the stack - * trace element. - * - * @return A source file name. - */ - public String getFile() { - return file; - } - - /** - * Gets the line number where an {@link Err} occured. - * - * This line number can be a negative number if this is unavailable in the - * stack trace element. - * - * @return A line number. - */ - public int getLine() { - return line; - } -} - diff --git a/src/main/java/com/github/sttk/sabi/errs/notify/ErrNotifier.java b/src/main/java/com/github/sttk/sabi/errs/notify/ErrNotifier.java deleted file mode 100644 index d6b8153..0000000 --- a/src/main/java/com/github/sttk/sabi/errs/notify/ErrNotifier.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * ErrNotifier class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.errs.notify; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.errs.ErrOcc; -import com.github.sttk.sabi.errs.ErrHandler; - -import java.util.List; -import java.util.LinkedList; - -/** - * {@code ErrNotifier} is the class that notifies {@link Err} creations to {@link - * ErrHandler}(s). - * This class manages a list for handlers that process a {@link Err} - * synchronously and another list for handlers that process it asynchronously. - * - * The only one instance of this class is created as a static field of {@link - * Err} class. - */ -public final class ErrNotifier { - - /** The flag that indicates whether this instance is fixed or not. */ - private boolean isFixed = false; - - /** - * The list that holds {@link ErrHandler} objects which is executed - * synchronously. - */ - protected final List syncErrHandlers = new LinkedList<>(); - - /** - * The list that holds {@link ErrHandler} objects which is executed - * asynchronously. - */ - protected final List asyncErrHandlers = new LinkedList<>(); - - /** - * Constructs an instance of this class with no argument. - */ - public ErrNotifier() {} - - /** - * Checks whether this object is fixed or not. - * - * @return [@code true} if this object is fixed. - */ - public boolean isFixed() { - return this.isFixed; - } - - /** - * Adds an {@link ErrHandler} object that is processed synchronously. - * After calling {@link #fix}, this method adds no more. - * - * @param handler An {@link ErrHandler} object. - */ - public void addSyncHandler(final ErrHandler handler) { - if (this.isFixed) { - return; - } - this.syncErrHandlers.add(handler); - } - - /** - * Adds an {@link ErrHandler} object that is processed asynchronously. - * After calling {@link #fix}, this method adds no more. - * - * @param handler An {@link ErrHandler} object. - */ - public void addAsyncHandler(final ErrHandler handler) { - if (this.isFixed) { - return; - } - this.asyncErrHandlers.add(handler); - } - - /** - * Fixes this object. - * This method makes it impossible to add more {@link ErrHandler}s to this - * object, and possible to notify that an {@link Err} object is created. - */ - public void fix() { - this.isFixed = true; - } - - /** - * Notifies that an {@link Err} object is created. - * However, this method does nothging until this object is fixed. - * - * @param err An {@link Err} object. - */ - public void notify(final Err err) { - if (!this.isFixed) { - return; - } - - final var occ = new ErrOcc(err); - - for (var handler : this.syncErrHandlers) { - handler.handle(err, occ); - } - - if (!this.asyncErrHandlers.isEmpty()) { - for (var handler : this.asyncErrHandlers) { - Thread.ofVirtual().start(() -> { - handler.handle(err, occ); - }); - } - } - } -} diff --git a/src/main/java/com/github/sttk/sabi/errs/package-info.java b/src/main/java/com/github/sttk/sabi/errs/package-info.java deleted file mode 100644 index a2e209f..0000000 --- a/src/main/java/com/github/sttk/sabi/errs/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * This package contains modules related to errors for sabi framework. - * - * @version 0.3 - */ -package com.github.sttk.sabi.errs; diff --git a/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java b/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java new file mode 100644 index 0000000..eec3d8f --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java @@ -0,0 +1,108 @@ +/* + * AsyncGroup class. + * Copyright (C) 2023-2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.Runner; + +import java.util.Map; +import java.util.HashMap; + +public class AsyncGroupImpl implements AsyncGroup { + private ExcEntry excHead; + private ExcEntry excLast; + private VthEntry vthHead; + private VthEntry vthLast; + String name; + + public AsyncGroupImpl() {} + + @Override + public void add(final Runner runner) { + final var name = this.name; + var vth = Thread.ofVirtual().start(() -> { + try { + runner.run(); + } catch (Exc | RuntimeException e) { + addExc(name, e); + } + }); + + var ent = new VthEntry(name, vth); + if (this.vthLast == null) { + this.vthHead = ent; + this.vthLast = ent; + } else { + this.vthLast.next = ent; + this.vthLast = ent; + } + } + + synchronized void addExc(String name, Exception e) { + var exc = (e instanceof Exc) ? Exc.class.cast(e) : new Exc(new RunnerFailed(), e); + var ent = new ExcEntry(name, exc); + + if (this.excLast == null) { + this.excHead = ent; + this.excLast = ent; + } else { + this.excLast.next = ent; + this.excLast = ent; + } + } + + void joinAndPutExcsInto(Map excMap) { + for (var ent = this.vthHead; ent != null; ent = ent.next) { + try { + ent.thread.join(); + } catch (InterruptedException e) { + addExc(ent.name, new Exc(new RunnerInterrupted(), e)); + } + } + for (var ent = this.excHead; ent != null; ent = ent.next) { + excMap.put(ent.name, ent.exc); + } + clear(); + } + + void joinAndIgnoreExcs() { + for (var ent = this.vthHead; ent != null; ent = ent.next) { + try { + ent.thread.join(); + } catch (InterruptedException e) {} + } + clear(); + } + + void clear() { + this.excHead = null; + this.excLast = null; + this.vthHead = null; + this.vthLast = null; + } +} + +class ExcEntry { + final String name; + final Exc exc; + ExcEntry next; + + ExcEntry(String name, Exc exc) { + this.name = name; + this.exc = exc; + } +} + +class VthEntry { + final String name; + final Thread thread; + VthEntry next; + + VthEntry(String name, Thread thread) { + this.name = name; + this.thread = thread; + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/DataConnContainer.java b/src/main/java/com/github/sttk/sabi/internal/DataConnContainer.java new file mode 100644 index 0000000..6513424 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataConnContainer.java @@ -0,0 +1,19 @@ +/* + * DataConnContainer class. + * Copyright (C) 2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.sabi.DataConn; + +public class DataConnContainer { + DataConnContainer prev; + DataConnContainer next; + String name; + DataConn conn; + + DataConnContainer(String name, DataConn conn) { + this.name = name; + this.conn = conn; + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/DataConnList.java b/src/main/java/com/github/sttk/sabi/internal/DataConnList.java new file mode 100644 index 0000000..e8c54ae --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataConnList.java @@ -0,0 +1,38 @@ +/* + * DataConnList class. + * Copyright (C) 2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.errs.Exc; + +public class DataConnList { + DataConnContainer head; + DataConnContainer last; + + public DataConnList() {} + + void appendContainer(DataConnContainer ptr) { + ptr.next = null; + + if (this.last == null) { + this.head = ptr; + this.last = ptr; + ptr.prev = null; + } else { + this.last.next = ptr; + ptr.prev = this.last; + this.last = ptr; + } + } + + void closeDataConns() { + var ptr = this.last; + while (ptr != null) { + ptr.conn.close(); + ptr = ptr.prev; + } + this.head = null; + this.last = null; + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java b/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java new file mode 100644 index 0000000..d09f7fe --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java @@ -0,0 +1,215 @@ +/* + * DataHubInner class. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.sabi.DataSrc; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.DataHub; +import com.github.sttk.errs.Exc; +import java.util.Map; +import java.util.HashMap; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DataHubInner { + + final static DataSrcList GLOBAL_DATA_SRC_LIST = new DataSrcList(false); + static AtomicBoolean GLOBAL_DATA_SRCS_FIXED = new AtomicBoolean(false); + + public static void usesGlobal(String name, DataSrc ds) { + if (! GLOBAL_DATA_SRCS_FIXED.get()) { + GLOBAL_DATA_SRC_LIST.addDataSrc(name, ds); + } + } + + public static AutoCloseable setupGlobals() throws Exc { + if (GLOBAL_DATA_SRCS_FIXED.compareAndSet(false, true)) { + var excMap = GLOBAL_DATA_SRC_LIST.setupDataSrcs(); + if (! excMap.isEmpty()) { + GLOBAL_DATA_SRC_LIST.closeDataSrcs(); + throw new Exc(new DataHub.FailToSetupGlobalDataSrcs(excMap)); + } + } + return new AutoShutdown(); + } + + static class AutoShutdown implements AutoCloseable { + @Override + public void close() { + GLOBAL_DATA_SRC_LIST.closeDataSrcs(); + } + } + + final DataSrcList localDataSrcList = new DataSrcList(true); + final Map dataSrcMap = new HashMap<>(); + final DataConnList dataConnList = new DataConnList(); + final Map dataConnMap = new HashMap<>(); + boolean fixed = false; + + public DataHubInner() { + GLOBAL_DATA_SRCS_FIXED.compareAndSet(false, true); + GLOBAL_DATA_SRC_LIST.copyContainerPtrsDidSetupInto(this.dataSrcMap); + } + + public void uses(String name, DataSrc ds) { + if (this.fixed) { + return; + } + + this.localDataSrcList.addDataSrc(name, ds); + } + + public void disuses(String name) { + if (this.fixed) { + return; + } + + var ptr = this.dataSrcMap.get(name); + if (ptr != null && ptr.local && Objects.equals(ptr.name, name)) { + this.dataSrcMap.remove(name); + } + + this.localDataSrcList.removeAndCloseContainerPtrDidSetupByName(name); + this.localDataSrcList.removeAndCloseContainerPtrNotSetupByName(name); + } + + public void close() { + this.dataConnMap.clear(); + this.dataConnList.closeDataConns(); + + this.dataSrcMap.clear(); + this.localDataSrcList.closeDataSrcs(); + } + + public void begin() throws Exc { + this.fixed = true; + + var excMap = this.localDataSrcList.setupDataSrcs(); + this.localDataSrcList.copyContainerPtrsDidSetupInto(this.dataSrcMap); + + if (! excMap.isEmpty()) { + throw new Exc(new DataHub.FailToSetupLocalDataSrcs(excMap)); + } + } + + public void commit() throws Exc { + var excMap = new HashMap(); + + var ag = new AsyncGroupImpl(); + var ptr = this.dataConnList.head; + while (ptr != null) { + ag.name = ptr.name; + try { + ptr.conn.preCommit(ag); + } catch (Exc e) { + excMap.put(ptr.name, e); + break; + } catch (RuntimeException e) { + excMap.put(ptr.name, new Exc(new DataHub.RuntimeExceptionOccured(), e)); + break; + } + ptr = ptr.next; + } + ag.joinAndPutExcsInto(excMap); + + if (! excMap.isEmpty()) { + throw new Exc(new DataHub.FailToPreCommitDataConn(excMap)); + } + + ag = new AsyncGroupImpl(); + ptr = this.dataConnList.head; + while (ptr != null) { + ag.name = ptr.name; + try { + ptr.conn.commit(ag); + } catch (Exc e) { + excMap.put(ptr.name, e); + break; + } catch (RuntimeException e) { + excMap.put(ptr.name, new Exc(new DataHub.RuntimeExceptionOccured(), e)); + break; + } + ptr = ptr.next; + } + ag.joinAndPutExcsInto(excMap); + + if (! excMap.isEmpty()) { + throw new Exc(new DataHub.FailToCommitDataConn(excMap)); + } + } + + public void rollback() { + var ag = new AsyncGroupImpl(); + var ptr = this.dataConnList.head; + while (ptr != null) { + ag.name = ptr.name; + if (ptr.conn.shouldForceBack()) { + ptr.conn.forceBack(ag); + } else { + ptr.conn.rollback(ag); + } + ptr = ptr.next; + } + + ag.joinAndIgnoreExcs(); + } + + public void postCommit() { + var ag = new AsyncGroupImpl(); + var ptr = this.dataConnList.head; + while (ptr != null) { + ag.name = ptr.name; + ptr.conn.postCommit(ag); + ptr = ptr.next; + } + + ag.joinAndIgnoreExcs(); + } + + public void end() { + this.dataConnMap.clear(); + this.dataConnList.closeDataConns(); + this.fixed = false; + } + + public C getDataConn(String name, Class cls) throws Exc { + var connPtr = this.dataConnMap.get(name); + if (connPtr != null) { + try { + return cls.cast(connPtr.conn); + } catch (Exception e) { + throw new Exc(new DataHub.FailToCastDataConn(name, cls.getName()), e); + } + } + + var dsPtr = this.dataSrcMap.get(name); + if (dsPtr == null) { + throw new Exc(new DataHub.NoDataSrcToCreateDataConn(name, cls.getName())); + } + + DataConn conn; + try { + conn = dsPtr.ds.createDataConn(); + } catch (Exc | RuntimeException e) { + throw new Exc(new DataHub.FailToCreateDataConn(name, cls.getName())); + } + if (conn == null) { + throw new Exc(new DataHub.CreatedDataConnIsNull(name, cls.getName())); + } + + C c; + try { + c = cls.cast(conn); + } catch (Exception e) { + throw new Exc(new DataHub.FailToCastDataConn(name, cls.getName()), e); + } + + connPtr = new DataConnContainer(name, c); + this.dataConnMap.put(name, connPtr); + this.dataConnList.appendContainer(connPtr); + + return c; + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/DataSrcContainer.java b/src/main/java/com/github/sttk/sabi/internal/DataSrcContainer.java new file mode 100644 index 0000000..0c07343 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataSrcContainer.java @@ -0,0 +1,21 @@ +/* + * DataSrcContainer class. + * Copyright (C) 2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.sabi.DataSrc; + +public class DataSrcContainer { + DataSrcContainer prev; + DataSrcContainer next; + boolean local; + String name; + DataSrc ds; + + DataSrcContainer(boolean local, String name, DataSrc ds) { + this.local = local; + this.name = name; + this.ds = ds; + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/DataSrcList.java b/src/main/java/com/github/sttk/sabi/internal/DataSrcList.java new file mode 100644 index 0000000..8dc373d --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataSrcList.java @@ -0,0 +1,174 @@ +/* + * DataSrcContainer class. + * Copyright (C) 2025 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.DataSrc; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class DataSrcList { + DataSrcContainer notSetupHead; + DataSrcContainer notSetupLast; + DataSrcContainer didSetupHead; + DataSrcContainer didSetupLast; + boolean local; + + DataSrcList(boolean local) { + this.local = local; + } + + void appendContainerPtrNotSetup(DataSrcContainer ptr) { + ptr.next = null; + + if (this.notSetupLast == null) { + this.notSetupHead = ptr; + this.notSetupLast = ptr; + ptr.prev = null; + } else { + this.notSetupLast.next = ptr; + ptr.prev = this.notSetupLast; + this.notSetupLast = ptr; + } + } + + void removeContainerPtrNotSetup(DataSrcContainer ptr) { + var prev = ptr.prev; + var next = ptr.next; + + if (prev == null && next == null) { + this.notSetupHead = null; + this.notSetupLast = null; + } else if (prev == null) { + next.prev = null; + this.notSetupHead = next; + } else if (next == null) { + prev.next = null; + this.notSetupLast = prev; + } else { + next.prev = prev; + prev.next = next; + } + } + + void removeAndCloseContainerPtrNotSetupByName(String name) { + var ptr = this.notSetupHead; + while (ptr != null) { + if (Objects.equals(ptr.name, name)) { + this.removeContainerPtrNotSetup(ptr); + ptr.ds.close(); + } + ptr = ptr.next; + } + } + + void appendContainerPtrDidSetup(DataSrcContainer ptr) { + ptr.next = null; + + if (this.didSetupLast == null) { + this.didSetupHead = ptr; + this.didSetupLast = ptr; + ptr.prev = null; + } else { + this.didSetupLast.next = ptr; + ptr.prev = this.didSetupLast; + this.didSetupLast = ptr; + } + } + + void removeContainerPtrDidSetup(DataSrcContainer ptr) { + var prev = ptr.prev; + var next = ptr.next; + + if (prev == null && next == null) { + this.didSetupHead = null; + this.didSetupLast = null; + } else if (prev == null) { + next.prev = null; + this.didSetupHead = next; + } else if (next == null) { + prev.next = null; + this.didSetupLast = prev; + } else { + next.prev = prev; + prev.next = next; + } + } + + void removeAndCloseContainerPtrDidSetupByName(String name) { + var ptr = this.didSetupHead; + while (ptr != null) { + if (Objects.equals(ptr.name, name)) { + removeContainerPtrDidSetup(ptr); + ptr.ds.close(); + } + ptr = ptr.next; + } + } + + void copyContainerPtrsDidSetupInto(Map m) { + var ptr = this.didSetupHead; + while (ptr != null) { + m.put(ptr.name, ptr); + ptr = ptr.next; + } + } + + void addDataSrc(String name, DataSrc ds) { + var ptr = new DataSrcContainer(this.local, name, ds); + this.appendContainerPtrNotSetup(ptr); + } + + Map setupDataSrcs() { + var excMap = new HashMap(); + + if (this.notSetupHead == null) { + return excMap; + } + + var ag = new AsyncGroupImpl(); + + var ptr = this.notSetupHead; + while (ptr != null) { + ag.name = ptr.name; + try { + ptr.ds.setup(ag); + } catch (Exc exc) { + excMap.put(ptr.name, exc); + break; + } + ptr = ptr.next; + } + + ag.joinAndPutExcsInto(excMap); + + var firstPtrNotSetupYet = ptr; + + ptr = this.notSetupHead; + while (ptr != null && ptr != firstPtrNotSetupYet) { + var next = ptr.next; + if (! excMap.containsKey(ptr.name)) { + this.removeContainerPtrNotSetup(ptr); + this.appendContainerPtrDidSetup(ptr); + } + ptr = next; + } + + return excMap; + } + + void closeDataSrcs() { + var ptr = this.didSetupLast; + while (ptr != null) { + ptr.ds.close(); + ptr = ptr.prev; + } + this.notSetupHead = null; + this.notSetupLast = null; + this.didSetupHead = null; + this.didSetupLast = null; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 376fc07..deab0bf 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,12 +1,15 @@ /* - * module-info class. - * Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. + * module-info. + * Copyright (C) 2022-2025 Takayuki Sato. All Rights Reserved. */ /** - * Defines the module of sabi framework. + * Defines the APIs of Sabi framework. + * + * This module includes the interfaces that abstracts data accesses to the external data stores + * and the classes to execute a logic function with or without transaction operations. */ module com.github.sttk.sabi { exports com.github.sttk.sabi; - exports com.github.sttk.sabi.errs; + requires transitive com.github.sttk.errs; } diff --git a/src/test/java/ReadmeTest.java b/src/test/java/ReadmeTest.java deleted file mode 100644 index e98b349..0000000 --- a/src/test/java/ReadmeTest.java +++ /dev/null @@ -1,266 +0,0 @@ -import com.github.sttk.sabi.*; -import com.github.sttk.sabi.errs.*; - -import java.util.Map; -import java.util.HashMap; -import java.time.OffsetTime; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -public class ReadmeTest { - - @Test void testGreetLogic_morning() { - var base = new MapGreetDaxBase(); - base.m.put("username", "everyone"); - base.m.put("hour", 10); - - try (base) { - base.txn(new GreetLogic()); - } catch (Err e) { - fail(e.toString()); - } - - assertEquals(base.m.get("greeting"), "Good morning, everyone.\n"); - } - - @Test void testGreetApp_main() { - setenv("GREETING_USERNAME", "foo"); - setenv("GREETING_HOUR", "10"); - - try (var ac = Sabi.startApp()) { - executeApp(); - } catch (Exception e) { - //System.err.println(e.toString()); - //System.exit(1); - fail(e.toString()); - } - } - void executeApp() throws Err { - try (var base = new GreetDaxBase()) { - base.uses("memory", new MemoryDaxSrc()); - base.txn(new GreetLogic()); - } - } - - static void setenv(String name, String value) { - try { - String os = System.getProperty("os.name").toLowerCase(); - - if (os.startsWith("win")) { - var cls = Class.forName("java.lang.ProcessEnvironment"); - var fld = cls.getDeclaredField("theCaseInsensitiveEnvironment"); - fld.setAccessible(true); - @SuppressWarnings("unchecked") - var map = (Map) fld.get(null); - - if (value == null) { - map.remove(name); - } else { - map.put(name, value); - } - - } else { - var cls = Class.forName("java.lang.ProcessEnvironment$Variable"); - var mtd = cls.getDeclaredMethod("valueOf", String.class); - mtd.setAccessible(true); - var envName = mtd.invoke(null, name); - - cls = Class.forName("java.lang.ProcessEnvironment$Value"); - mtd = cls.getDeclaredMethod("valueOf", String.class); - mtd.setAccessible(true); - var envValue = mtd.invoke(null, value); - - cls = Class.forName("java.lang.ProcessEnvironment"); - var fld = cls.getDeclaredField("theEnvironment"); - fld.setAccessible(true); - @SuppressWarnings("unchecked") - var map = (Map) fld.get(null); - - if (value == null) { - map.remove(envName); - } else { - map.put(envName, envValue); - } - } - } catch (RuntimeException | Error e) { - throw e; - } catch (Exception e) { - var re = new RuntimeException(e); - re.setStackTrace(e.getStackTrace()); - throw re; - } - } -} - -interface GreetDax { - record NoName() {} - record FailToGetHour() {} - record FailToOutput(String text) {} - - String getUserName() throws Err; - int getHour() throws Err; - void output(String text) throws Err; -} - -class GreetLogic implements Logic { - @Override public void run(GreetDax dax) throws Err { - int hour = dax.getHour(); - - String s; - if (5 <= hour && hour < 12) { - s = "Good morning, "; - } else if (12 <= hour && hour < 16) { - s = "Good afternoon, "; - } else if (16 <= hour && hour < 21) { - s = "Good evening, "; - } else { - s = "Hi, "; - } - dax.output(s); - - var name = dax.getUserName(); - dax.output(name + ".\n"); - } -} - -class MapGreetDaxBase extends DaxBase implements GreetDax { - Map m = new HashMap<>(); - - @Override public String getUserName() throws Err { - var name = this.m.get("username"); - if (name == null) { - throw new Err(new NoName()); - } - return String.class.cast(name); - } - - @Override public int getHour() throws Err { - var hour = this.m.get("hour"); - if (hour == null) { - throw new Err(new FailToGetHour()); - } - return Integer.class.cast(hour); - } - - @Override public void output(String text) throws Err { - String s = ""; - var v = this.m.get("greeting"); - if ("error".equals(v)) { - throw new Err(new FailToOutput(text)); - } else if (v != null) { - s += v; - } - this.m.put("greeting", s + text); - } -} - -interface EnvVarsDax extends GreetDax, Dax { - @Override default String getUserName() throws Err { - var u = System.getenv("GREETING_USERNAME"); - if (u == null || u.isBlank()) { - throw new Err(new NoName()); - } - return u; - } - - //@Override default int getHour() throws Err { - // var h = System.getenv("GREETING_HOUR"); - // try { - // return Integer.valueOf(h); - // } catch (Exception e) { - // throw new Err(new FailToGetHour(), e); - // } - //} -} - -//interface ConsoleDax extends GreetDax, Dax { -// @Override default void output(String text) throws Err { -interface ConsoleDax extends PrintDax, Dax { - @Override default void print(String text) throws Err { - System.out.print(text); - } -} - -class GreetDaxBase extends DaxBase - implements EnvVarsDax, SystemClockDax, MemoryDax, ConsoleDax {} - -interface SystemClockDax extends GreetDax, Dax { - @Override default int getHour() throws Err { - return OffsetTime.now().getHour(); - } -} - -interface PrintDax { - String getText() throws Err; - void print(String text) throws Err; -} - -class PrintLogic implements Logic { - @Override public void run(PrintDax dax) throws Err { - var text = dax.getText(); - dax.print(text); - } -} - -class MemoryDaxSrc implements DaxSrc { - StringBuilder buf = new StringBuilder(); - - @Override public void setup(AsyncGroup ag) throws Err { - } - - @Override public void close() { - buf.setLength(0); - } - - @Override public DaxConn createDaxConn() throws Err { - return new MemoryDaxConn(buf); - } -} - -class MemoryDaxConn implements DaxConn { - StringBuilder buf; - - public MemoryDaxConn(StringBuilder buf) { - this.buf = buf; - } - - public void append(String text) { - this.buf.append(text); - } - - public String get() { - return this.buf.toString(); - } - - @Override public void commit(AsyncGroup ag) throws Err { - } - - @Override public boolean isCommitted() { - return true; - } - - @Override public void rollback(AsyncGroup ag) { - } - - @Override public void forceBack(AsyncGroup ag) { - buf.setLength(0); - } - - @Override public void close() { - } -} - -interface MemoryDax extends GreetDax, PrintDax, Dax { - @Override default void output(String text) throws Err { - MemoryDaxConn conn = getDaxConn("memory"); - conn.append(text); - } - - @Override default String getText() throws Err { - MemoryDaxConn conn = getDaxConn("memory"); - return conn.get(); - } -} - diff --git a/src/test/java/com/github/sttk/sabi/DaxBaseTest.java b/src/test/java/com/github/sttk/sabi/DaxBaseTest.java deleted file mode 100644 index a4c1d3b..0000000 --- a/src/test/java/com/github/sttk/sabi/DaxBaseTest.java +++ /dev/null @@ -1,1014 +0,0 @@ -package com.github.sttk.sabi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; - -import java.util.Map; -import java.util.HashMap; -import java.util.List; -import java.util.ArrayList; -import java.lang.reflect.Field; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.DaxBase.*; -import com.github.sttk.sabi.DaxBaseTest.*; -import static com.github.sttk.sabi.DaxBaseTest.*; - -public class DaxBaseTest { - - record FailToSetupFooDaxSrc() {} - record FailToSetupBarDaxSrc() {} - record FailToCreateFooDaxConn() {} - record FailToCreateBarDaxConn() {} - record CreatedFooDaxConnIsNull() {} - record CreatedBarDaxConnIsNull() {} - record FailToCommitFooDaxConn() {} - record FailToCommitBarDaxConn() {} - record FailToCommitQuxDaxConn() {} - - static List logs = new ArrayList<>(); - - static boolean willFailToSetupFooDaxSrc = false; - static boolean willFailToSetupBarDaxSrc = false; - static boolean willFailToCreateFooDaxConn = false; - static boolean willFailToCreateBarDaxConn = false; - static boolean willCreatedFooDaxConnBeNull = false; - static boolean willCreatedBarDaxConnBeNull = false; - static boolean willFailToCommitFooDaxConn = false; - static boolean willFailToCommitBarDaxConn = false; - - static boolean getIsGlobalDaxSrcsFixed() { - final var c = DaxBase.class; - try { - final var f = c.getDeclaredField("isGlobalDaxSrcsFixed"); - f.setAccessible(true); - return f.getBoolean(null); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static void setIsGlobalDaxSrcsFixed(boolean b) { - final var c = DaxBase.class; - try { - final var f = c.getDeclaredField("isGlobalDaxSrcsFixed"); - f.setAccessible(true); - f.setBoolean(null, b); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static Map getGlobalDaxSrcMap() { - final var c = DaxBase.class; - try { - final Field f = c.getDeclaredField("globalDaxSrcMap"); - f.setAccessible(true); - @SuppressWarnings("unchecked") - final var m = (Map) f.get(null); - return m; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static boolean getIsLocalDaxSrcsFixed(DaxBase base) { - final var c = DaxBase.class; - try { - final var f = c.getDeclaredField("isLocalDaxSrcsFixed"); - f.setAccessible(true); - return f.getBoolean(base); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static void setIsLocalDaxSrcsFixed(DaxBase base, boolean b) { - final var c = DaxBase.class; - try { - final var f = c.getDeclaredField("isLocalDaxSrcsFixed"); - f.setAccessible(true); - f.setBoolean(base, b); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static Map getLocalDaxSrcMap(DaxBase base) { - final var c = DaxBase.class; - try { - final Field f = c.getDeclaredField("localDaxSrcMap"); - f.setAccessible(true); - @SuppressWarnings("unchecked") - final var m = (Map) f.get(base); - return m; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static class FooDaxSrc implements DaxSrc { - @Override - public void setup(AsyncGroup ag) throws Err { - if (willFailToSetupFooDaxSrc) { - throw new Err(new FailToSetupFooDaxSrc()); - } - logs.add("FooDaxSrc#setup"); - } - @Override - public void close() { - logs.add("FooDaxSrc#close"); - } - @Override - public DaxConn createDaxConn() throws Err { - if (willFailToCreateFooDaxConn) { - throw new Err(new FailToCreateFooDaxConn()); - } - if (willCreatedFooDaxConnBeNull) { - return null; - } - logs.add("FooDaxSrc#createDaxConn"); - return new FooDaxConn(); - } - } - - static class FooDaxConn implements DaxConn { - boolean committed = false; - @Override - public void commit(AsyncGroup ag) throws Err { - if (willFailToCommitFooDaxConn) { - throw new Err(new FailToCommitFooDaxConn()); - } - logs.add("FooDaxConn#commit"); - committed = true; - } - @Override - public boolean isCommitted() { - return committed; - } - @Override - public void rollback(AsyncGroup ag) { - logs.add("FooDaxConn#rollback"); - } - @Override - public void forceBack(AsyncGroup ag) { - logs.add("FooDaxConn#forceBack"); - } - @Override - public void close() { - logs.add("FooDaxConn#close"); - } - } - - static class BarDaxSrc implements DaxSrc { - @Override - public void setup(AsyncGroup ag) throws Err { - ag.add(() -> { - if (willFailToSetupBarDaxSrc) { - throw new Err(new FailToSetupBarDaxSrc()); - } - logs.add("BarDaxSrc#setup"); - }); - } - @Override - public void close() { - logs.add("BarDaxSrc#close"); - } - @Override - public DaxConn createDaxConn() throws Err { - if (willFailToCreateBarDaxConn) { - throw new Err(new FailToCreateBarDaxConn()); - } - if (willCreatedBarDaxConnBeNull) { - return null; - } - logs.add("BarDaxSrc#createDaxConn"); - return new BarDaxConn(); - } - } - - static class BarDaxConn implements DaxConn { - boolean committed = false; - @Override - public void commit(AsyncGroup ag) throws Err { - ag.add(() -> { - if (willFailToCommitBarDaxConn) { - throw new Err(new FailToCommitBarDaxConn()); - } - logs.add("BarDaxConn#commit"); - committed = true; - }); - } - @Override - public boolean isCommitted() { - return committed; - } - @Override - public void rollback(AsyncGroup ag) { - ag.add(() -> { - logs.add("BarDaxConn#rollback"); - }); - } - @Override - public void forceBack(AsyncGroup ag) { - ag.add(() -> { - logs.add("BarDaxConn#forceBack"); - }); - } - @Override - public void close() { - logs.add("BarDaxConn#close"); - } - } - - static class QuxDaxSrc implements DaxSrc { - @Override - public void setup(AsyncGroup ag) throws Err { - logs.add("QuxDaxSrc#setup"); - } - @Override - public void close() { - logs.add("QuxDaxSrc#close"); - } - @Override - public DaxConn createDaxConn() throws Err { - logs.add("QuxDaxSrc#createDaxConn"); - return new QuxDaxConn(); - } - } - - static class QuxDaxConn implements DaxConn { - @Override - public void commit(AsyncGroup ag) throws Err { - throw new Err(new FailToCommitQuxDaxConn()); - } - @Override - public void forceBack(AsyncGroup ag) { - logs.add("QuxDaxConn#forceBack"); - } - @Override - public void close() { - logs.add("QuxDaxConn#close"); - } - } - - /// - - @BeforeEach - void reset() { - setIsGlobalDaxSrcsFixed(false); - getGlobalDaxSrcMap().clear(); - - willFailToSetupFooDaxSrc = false; - willFailToSetupBarDaxSrc = false; - willFailToCreateFooDaxConn = false; - willFailToCreateBarDaxConn = false; - willCreatedFooDaxConnBeNull = false; - willCreatedBarDaxConnBeNull = false; - willFailToCommitFooDaxConn = false; - willFailToCommitBarDaxConn = false; - - logs.clear(); - } - - /// - - @Test - public void new_and_close() { - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - - var base = new DaxBase(); - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(getIsLocalDaxSrcsFixed(base)).isFalse(); - assertThat(getLocalDaxSrcMap(base)).hasSize(0); - - base.close(); - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(getIsLocalDaxSrcsFixed(base)).isFalse(); - assertThat(getLocalDaxSrcMap(base)).hasSize(0); - - assertThat(logs).hasSize(0); - } - - @Test - public void uses_ok() { - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(getGlobalDaxSrcMap()).hasSize(0); - - try (var base = new DaxBase()) { - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(getIsLocalDaxSrcsFixed(base)).isFalse(); - assertThat(getLocalDaxSrcMap(base)).hasSize(0); - - base.uses("httpRequest", new FooDaxSrc()); - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(getIsLocalDaxSrcsFixed(base)).isFalse(); - var m = getLocalDaxSrcMap(base); - assertThat(m).hasSize(1); - assertThat(m.get("httpRequest")).isInstanceOf(FooDaxSrc.class); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void uses_failToSetupDaxSrc_sync() { - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(getGlobalDaxSrcMap()).hasSize(0); - - willFailToSetupFooDaxSrc = true; - - try (var base = new DaxBase()) { - base.uses("httpRequest", new FooDaxSrc()); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToSetupLocalDaxSrc r: { - assertThat(r.name()).isEqualTo("httpRequest"); - var e1 = Err.class.cast(e.getCause()); - assertThat(e1.getReason()).isInstanceOf(FailToSetupFooDaxSrc.class); - break; - } - default: { - fail(); - break; - } - } - } - - assertThat(logs).hasSize(0); - } - - @Test - public void uses_failToSetupDaxSrc_async() { - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(getGlobalDaxSrcMap()).hasSize(0); - - willFailToSetupBarDaxSrc = true; - - try (var base = new DaxBase()) { - base.uses("httpRequest", new BarDaxSrc()); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToSetupLocalDaxSrc r: { - assertThat(r.name()).isEqualTo("httpRequest"); - var e1 = Err.class.cast(e.getCause()); - assertThat(e1.getReason()).isInstanceOf(FailToSetupBarDaxSrc.class); - break; - } - default: { - fail(); - break; - } - } - } - - assertThat(logs).hasSize(0); - } - - @Test - public void uses_txnAlreadyBegan() { - try (var base = new DaxBase()) { - base.begin(); - base.uses("httpRequest", new BarDaxSrc()); - base.end(); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(0); - } - - @Test - public void close_txnAlreadyBegan() { - try { - var base = new DaxBase(); - base.uses("httpRequest", new FooDaxSrc()); - base.begin(); - base.close(); - base.end(); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - } - - @Test - public void disuses_ok() { - try { - var base = new DaxBase(); - base.uses("httpRequest", new FooDaxSrc()); - base.disuses("httpRequest"); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void disuses_ignoreIfNoDaxSrc() { - try { - var base = new DaxBase(); - base.uses("httpRequest", new FooDaxSrc()); - base.disuses("xxx"); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - } - - @Test - public void disuses_txnAlreadyBegan() { - try { - var base = new DaxBase(); - base.uses("httpRequest", new FooDaxSrc()); - base.begin(); - base.disuses("httpRequest"); - base.end(); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - } - - @Test - public void txn_ok() { - try (var base = new DaxBase()) { - base.txn((Dax dax) -> { - logs.add("exec logic"); - }); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("exec logic"); - } - - @Test - public void txn_withCustomDax() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn((MyLogicDax dax) -> { - var s = dax.getData(); - dax.setData(s); - }); - } catch (Err e) { - fail(e); - } - - assertThat(logs).hasSize(5); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#createDaxConn"); - assertThat(logs.get(2)).isEqualTo("FooDaxConn#commit"); - assertThat(logs.get(3)).isEqualTo("FooDaxConn#close"); - assertThat(logs.get(4)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_failToCastDaxBase() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - class MyLogic implements Logic { - @Override public void run(MyLogicDax dax) throws Err { - var s = dax.getData(); - dax.setData(s); - } - } - - interface AllLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn(new MyLogic()); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToRunLogic r: { - assertThat(r.logicType()).isEqualTo(MyLogic.class); - assertThat(e.getCause()).isInstanceOf(ClassCastException.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_failToCastDaxConn() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - class MyLogic implements Logic { - @Override public void run(MyLogicDax dax) throws Err { - var s = dax.getData(); - dax.setData(s); - } - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - BarDaxConn conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn(new MyLogic()); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToRunLogic r: { - assertThat(r.logicType()).isEqualTo(MyLogic.class); - assertThat(e.getCause()).isInstanceOf(ClassCastException.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(5); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#createDaxConn"); - assertThat(logs.get(2)).isEqualTo("FooDaxConn#rollback"); - assertThat(logs.get(3)).isEqualTo("FooDaxConn#close"); - assertThat(logs.get(4)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_errorFromLogic() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - BarDaxConn conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - record FailToDoSomething() {} - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn((MyLogicDax dax) -> { - throw new Err(new FailToDoSomething()); - }); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToDoSomething r: { - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_failToCreateDaxConn() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - class MyLogic implements Logic { - @Override public void run(MyLogicDax dax) throws Err { - var s = dax.getData(); - dax.setData(s); - } - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - willFailToCreateFooDaxConn = true; - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn(new MyLogic()); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToCreateDaxConn r: { - assertThat(r.name()).isEqualTo("mydata"); - var r2 = Err.class.cast(e.getCause()).getReason(); - assertThat(r2).isInstanceOf(FailToCreateFooDaxConn.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_errorBecauseCreatedDaxConnIsNull() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - class MyLogic implements Logic { - @Override public void run(MyLogicDax dax) throws Err { - var s = dax.getData(); - dax.setData(s); - } - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - willCreatedFooDaxConnBeNull = true; - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn(new MyLogic()); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case CreatedDaxConnIsNull r: { - assertThat(r.name()).isEqualTo("mydata"); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_failBecauseDaxSrcIsNotFound() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - try (var base = new MyDaxBase()) { - base.txn((MyLogicDax dax) -> { - var s = dax.getData(); - dax.setData(s); - }); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case DaxSrcIsNotFound r: { - assertThat(r.name()).isEqualTo("mydata"); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(0); - } - - @Test - public void txn_failToCommitDaxConn_sync() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - willFailToCommitFooDaxConn = true; - - try (var base = new MyDaxBase()) { - base.uses("mydata", new FooDaxSrc()); - base.txn((MyLogicDax dax) -> { - var s = dax.getData(); - dax.setData(s); - }); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToCommitDaxConn r: { - var m = r.errors(); - assertThat(m).hasSize(1); - var r1 = m.get("mydata").getReason(); - assertThat(r1).isInstanceOf(FailToCommitFooDaxConn.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(5); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#createDaxConn"); - assertThat(logs.get(2)).isEqualTo("FooDaxConn#rollback"); - assertThat(logs.get(3)).isEqualTo("FooDaxConn#close"); - assertThat(logs.get(4)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void txn_failToCommitDaxConn_async() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - willFailToCommitBarDaxConn = true; - - try (var base = new MyDaxBase()) { - base.uses("mydata", new BarDaxSrc()); - base.txn((MyLogicDax dax) -> { - var s = dax.getData(); - dax.setData(s); - }); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToCommitDaxConn r: { - var m = r.errors(); - assertThat(m).hasSize(1); - var r1 = m.get("mydata").getReason(); - assertThat(r1).isInstanceOf(FailToCommitBarDaxConn.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(5); - assertThat(logs.get(0)).isEqualTo("BarDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("BarDaxSrc#createDaxConn"); - assertThat(logs.get(2)).isEqualTo("BarDaxConn#rollback"); - assertThat(logs.get(3)).isEqualTo("BarDaxConn#close"); - assertThat(logs.get(4)).isEqualTo("BarDaxSrc#close"); - } - - @Test - public void txn_forceBack() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("foo"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("bar"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - willFailToCommitBarDaxConn = true; - - try (var base = new MyDaxBase()) { - base.uses("foo", new FooDaxSrc()); - base.uses("bar", new BarDaxSrc()); - base.txn((MyLogicDax dax) -> { - var s = dax.getData(); - dax.setData(s); - }); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToCommitDaxConn r: { - var m = r.errors(); - assertThat(m).hasSize(1); - var r1 = m.get("bar").getReason(); - assertThat(r1).isInstanceOf(FailToCommitBarDaxConn.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(11); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("BarDaxSrc#setup"); - assertThat(logs.get(2)).isEqualTo("FooDaxSrc#createDaxConn"); - assertThat(logs.get(3)).isEqualTo("BarDaxSrc#createDaxConn"); - assertThat(logs.get(4)).isEqualTo("FooDaxConn#commit"); - assertThat(logs.get(5)).isEqualTo("FooDaxConn#forceBack"); - assertThat(logs.get(6)).isEqualTo("BarDaxConn#rollback"); - assertThat(logs.get(7)).isEqualTo("FooDaxConn#close"); - assertThat(logs.get(8)).isEqualTo("BarDaxConn#close"); - assertThat(logs.get(9)).isEqualTo("FooDaxSrc#close"); - assertThat(logs.get(10)).isEqualTo("BarDaxSrc#close"); - } - - @Test - public void txn_whenDaxConnHasNoRollbackMechanism() { - interface MyLogicDax { - String getData() throws Err; - void setData(String v) throws Err; - } - - interface AllLogicDax extends MyLogicDax {} - - interface MyDax extends Dax, AllLogicDax { - default String getData() throws Err { - var conn = getDaxConn("mydata"); - return "hello"; - } - default void setData(String v) throws Err { - var conn = getDaxConn("mydata"); - } - } - - class MyDaxBase extends DaxBase implements MyDax {} - - try (var base = new MyDaxBase()) { - base.uses("mydata", new QuxDaxSrc()); - base.txn((MyLogicDax dax) -> { - var s = dax.getData(); - dax.setData(s); - }); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToCommitDaxConn r: { - var m = r.errors(); - assertThat(m).hasSize(1); - var r1 = m.get("mydata").getReason(); - assertThat(r1).isInstanceOf(FailToCommitQuxDaxConn.class); - break; - } - default: { - fail(e); - } - } - } - - assertThat(logs).hasSize(5); - assertThat(logs.get(0)).isEqualTo("QuxDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("QuxDaxSrc#createDaxConn"); - assertThat(logs.get(2)).isEqualTo("QuxDaxConn#forceBack"); - assertThat(logs.get(3)).isEqualTo("QuxDaxConn#close"); - assertThat(logs.get(4)).isEqualTo("QuxDaxSrc#close"); - } -} diff --git a/src/test/java/com/github/sttk/sabi/ParaTest.java b/src/test/java/com/github/sttk/sabi/ParaTest.java deleted file mode 100644 index 8d02122..0000000 --- a/src/test/java/com/github/sttk/sabi/ParaTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.github.sttk.sabi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; - -import com.github.sttk.sabi.errs.Err; - -import java.util.ArrayList; - -public class ParaTest { - - record FailToDoSomething() {} - - @Test - void should_run_argument_runners_in_parallel() throws Exception { - var logs = new ArrayList(); - Para.run(() -> { - try { Thread.sleep(200); } catch (InterruptedException e) {} - logs.add("1"); - }, () -> { - try { Thread.sleep(20); } catch (InterruptedException e) {} - logs.add("2"); - }); - assertThat(logs).containsExactly("2", "1"); - } - - @Test - void should_throw_an_Err_if_one_of_runners_causes_an_Err() { - var logs = new ArrayList(); - try { - Para.run(() -> { - throw new Err(new FailToDoSomething()); - }, () -> { - logs.add("2"); - }); - } catch (Err e) { - var r = Para.FailToRunInParallel.class.cast(e.getReason()); - assertThat(r.errors()).hasSize(1); - assertThat(r.errors().get(0).getReason()) - .isInstanceOf(FailToDoSomething.class); - } - } - - @Test - void should_throw_an_Err_if_one_of_runners_causes_an_RuntimeException() { - var logs = new ArrayList(); - try { - Para.run(() -> { - throw new RuntimeException(); - }, () -> { - logs.add("2"); - }); - } catch (Err e) { - var r = Para.FailToRunInParallel.class.cast(e.getReason()); - assertThat(r.errors()).hasSize(1); - assertThat(r.errors().get(0).getReason()) - .isInstanceOf(Para.RunInParallelExceptionOccurs.class); - assertThat(r.errors().get(0).getCause()) - .isInstanceOf(RuntimeException.class); - } - assertThat(logs).containsExactly("2"); - } - - @Test - void should_run_holding_runners_in_parallel() throws Exception { - var logs = new ArrayList(); - var para = new Para(() -> { - try { Thread.sleep(200); } catch (InterruptedException e) {} - logs.add("1"); - }, () -> { - try { Thread.sleep(20); } catch (InterruptedException e) {} - logs.add("2"); - }); - assertThat(logs).isEmpty(); - para.run(); - assertThat(logs).containsExactly("2", "1"); - } -} diff --git a/src/test/java/com/github/sttk/sabi/SabiTest.java b/src/test/java/com/github/sttk/sabi/SabiTest.java deleted file mode 100644 index 8119f7a..0000000 --- a/src/test/java/com/github/sttk/sabi/SabiTest.java +++ /dev/null @@ -1,390 +0,0 @@ -package com.github.sttk.sabi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; - -import java.util.Map; -import java.util.List; -import java.util.ArrayList; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.DaxBase.*; -import com.github.sttk.sabi.DaxBaseTest.*; -import static com.github.sttk.sabi.DaxBaseTest.*; - -public class SabiTest { - - @BeforeEach - void reset() { - new DaxBaseTest().reset(); - } - - /// - - @Test - public void should_register_global_DaxSrc() { - Sabi.uses("cliargs", new FooDaxSrc()); - - final Map m = getGlobalDaxSrcMap(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - - Sabi.uses("database", new BarDaxSrc()); - - assertThat(m).hasSize(2); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(m.get("database")).isInstanceOf(BarDaxSrc.class); - } - - @Test - public void should_register_global_DaxSrc_but_name_already_exists() { - Sabi.uses("cliargs", new FooDaxSrc()); - - final Map m = getGlobalDaxSrcMap(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - - Sabi.uses("cliargs", new BarDaxSrc()); - - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - } - - @Test - public void should_setup_zero_global_DaxSrc() { - final Map m = getGlobalDaxSrcMap(); - - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(m).hasSize(0); - assertThat(logs).hasSize(0); - - try { - Sabi.setup(); - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(0); - assertThat(logs).hasSize(0); - } - - @Test - public void should_setup_one_global_DaxSrc() { - final Map m = getGlobalDaxSrcMap(); - - Sabi.uses("cliargs", new FooDaxSrc()); - - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(0); - - try { - Sabi.setup(); - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void should_setup_multiple_global_DaxSrc() { - final Map m = getGlobalDaxSrcMap(); - - Sabi.uses("cliargs", new FooDaxSrc()); - Sabi.uses("database", new BarDaxSrc()); - - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(m).hasSize(2); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(m.get("database")).isInstanceOf(BarDaxSrc.class); - assertThat(logs).hasSize(0); - - try { - Sabi.setup(); - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(2); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(m.get("database")).isInstanceOf(BarDaxSrc.class); - assertThat(logs).hasSize(4); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("BarDaxSrc#setup"); - assertThat(logs.get(2)).isEqualTo("FooDaxSrc#close"); - assertThat(logs.get(3)).isEqualTo("BarDaxSrc#close"); - } - - @Test - public void should_setup_and_cannot_add_after_setup() { - final Map m = getGlobalDaxSrcMap(); - - Sabi.uses("cliargs", new FooDaxSrc()); - - assertThat(getIsGlobalDaxSrcsFixed()).isFalse(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(0); - - try { - Sabi.setup(); - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - - Sabi.uses("database", new FooDaxSrc()); - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void should_setup_sync_but_error() { - final Map m = getGlobalDaxSrcMap(); - - willFailToSetupFooDaxSrc = true; - - Sabi.uses("cliargs", new FooDaxSrc()); - - try { - Sabi.setup(); - fail(); - } catch (Err e) { - assertThat(e.getReason()).isInstanceOf(FailToSetupGlobalDaxSrcs.class); - switch (e.getReason()) { - case FailToSetupGlobalDaxSrcs r: { - assertThat(r.errors()).hasSize(1); - var e1 = r.errors().get("cliargs"); - assertThat(e1.getReason()).isInstanceOf(FailToSetupFooDaxSrc.class); - break; - } - default: { - fail(); - break; - } - } - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void should_setup_async_but_error() { - final Map m = getGlobalDaxSrcMap(); - - willFailToSetupBarDaxSrc = true; - - Sabi.uses("cliargs", new BarDaxSrc()); - - try { - Sabi.setup(); - fail(); - } catch (Err e) { - assertThat(e.getReason()).isInstanceOf(FailToSetupGlobalDaxSrcs.class); - switch (e.getReason()) { - case FailToSetupGlobalDaxSrcs r: { - assertThat(r.errors()).hasSize(1); - var e1 = r.errors().get("cliargs"); - assertThat(e1.getReason()).isInstanceOf(FailToSetupBarDaxSrc.class); - break; - } - default: { - fail(); - break; - } - } - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("cliargs")).isInstanceOf(BarDaxSrc.class); - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("BarDaxSrc#close"); - } - - @Test - public void should_setup_sync_and_async_but_error() { - final Map m = getGlobalDaxSrcMap(); - - willFailToSetupFooDaxSrc = true; - willFailToSetupBarDaxSrc = true; - - Sabi.uses("cliargs", new BarDaxSrc()); - Sabi.uses("database", new FooDaxSrc()); - - try { - Sabi.setup(); - fail(); - } catch (Err e) { - assertThat(e.getReason()).isInstanceOf(FailToSetupGlobalDaxSrcs.class); - switch (e.getReason()) { - case FailToSetupGlobalDaxSrcs r: { - assertThat(r.errors()).hasSize(2); - var e1 = r.errors().get("cliargs"); - assertThat(e1.getReason()).isInstanceOf(FailToSetupBarDaxSrc.class); - var e2 = r.errors().get("database"); - assertThat(e2.getReason()).isInstanceOf(FailToSetupFooDaxSrc.class); - break; - } - default: { - fail(); - break; - } - } - } catch (Exception e) { - fail(e); - } finally { - Sabi.close(); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(2); - assertThat(m.get("cliargs")).isInstanceOf(BarDaxSrc.class); - assertThat(m.get("database")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("BarDaxSrc#close"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void should_do_startApp_and_ok() { - final Map m = getGlobalDaxSrcMap(); - - Sabi.uses("database", new FooDaxSrc()); - - final Runner app = () -> { - logs.add("run app"); - }; - - try (var ac = Sabi.startApp()) { - app.run(); - } catch (Exception e) { - fail(e); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("database")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(3); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("run app"); - assertThat(logs.get(2)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void should_do_startApp_but_fail_to_setup() { - final Map m = getGlobalDaxSrcMap(); - - Sabi.uses("database", new FooDaxSrc()); - - willFailToSetupFooDaxSrc = true; - - final Runner app = () -> { - logs.add("run app"); - }; - - try (var c = Sabi.startApp()) { - app.run(); - fail(); - } catch (Err e) { - switch (e.getReason()) { - case FailToSetupGlobalDaxSrcs r: { - assertThat(r.errors()).hasSize(1); - var r2 = r.errors().get("database").getReason(); - assertThat(r2).isInstanceOf(FailToSetupFooDaxSrc.class); - break; - } - default: { - fail(e); - break; - } - } - } catch (Exception e) { - fail(e); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("database")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#close"); - } - - @Test - public void should_do_startApp_but_app_failed() { - final Map m = getGlobalDaxSrcMap(); - - Sabi.uses("database", new FooDaxSrc()); - - record FailToDoSomething() {} - - final Runner app = () -> { - throw new Err(new FailToDoSomething()); - }; - - try (var c = Sabi.startApp()) { - app.run(); - } catch (Err e) { - switch (e.getReason()) { - case FailToDoSomething r: { - break; - } - default: { - fail(e); - break; - } - } - } catch (Exception e) { - fail(e); - } - - assertThat(getIsGlobalDaxSrcsFixed()).isTrue(); - assertThat(m).hasSize(1); - assertThat(m.get("database")).isInstanceOf(FooDaxSrc.class); - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).isEqualTo("FooDaxSrc#setup"); - assertThat(logs.get(1)).isEqualTo("FooDaxSrc#close"); - } -} diff --git a/src/test/java/com/github/sttk/sabi/SeqTest.java b/src/test/java/com/github/sttk/sabi/SeqTest.java deleted file mode 100644 index dc5e7f6..0000000 --- a/src/test/java/com/github/sttk/sabi/SeqTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.sttk.sabi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; - -import com.github.sttk.sabi.errs.Err; - -import java.util.ArrayList; - -public class SeqTest { - - record FailToDoSomething() {} - - @Test - void should_run_argument_runners_sequencially() throws Exception { - var logs = new ArrayList(); - Seq.run(() -> logs.add("1"), () -> logs.add("2")); - assertThat(logs).containsExactly("1", "2"); - } - - @Test - void should_throw_an_Err_if_one_of_runners_causes_an_Err() { - var logs = new ArrayList(); - try { - Seq.run(() -> { - logs.add("1"); - }, () -> { - throw new Err(new FailToDoSomething()); - }); - } catch (Err e) { - assertThat(e.getReason()).isInstanceOf(FailToDoSomething.class); - } - assertThat(logs).containsExactly("1"); - } - - @Test - void should_run_holding_runners_sequentially() throws Exception { - var logs = new ArrayList(); - var seq = new Seq(() -> logs.add("1"), () -> logs.add("2")); - assertThat(logs).isEmpty(); - seq.run(); - assertThat(logs).containsExactly("1", "2"); - } -} diff --git a/src/test/java/com/github/sttk/sabi/async/AsyncGroupAsyncTest.java b/src/test/java/com/github/sttk/sabi/async/AsyncGroupAsyncTest.java deleted file mode 100644 index 15cbb61..0000000 --- a/src/test/java/com/github/sttk/sabi/async/AsyncGroupAsyncTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.github.sttk.sabi.async; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Nested; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.Runner; - -public class AsyncGroupAsyncTest { - - @Test - void should_be_ok() { - var ag = new AsyncGroupAsync(); - assertThat(ag.hasErr()).isFalse(); - - final boolean[] exec = {false}; - final Runner fn = () -> { - try { - Thread.sleep(50); - } catch (Exception e) {} - exec[0] = true; - }; - - ag.name = "foo"; - ag.add(fn); - assertThat(ag.hasErr()).isFalse(); - assertThat(exec[0]).isFalse(); - - ag.join(); - assertThat(ag.hasErr()).isFalse(); - assertThat(exec[0]).isTrue(); - - assertThat(ag.makeErrs()).hasSize(0); - assertThat(exec[0]).isTrue(); - } - - record FailToDoSomething() {} - - @Test - void should_be_error() { - var ag = new AsyncGroupAsync(); - assertThat(ag.hasErr()).isFalse(); - - final boolean[] exec = {false}; - final Runner fn = () -> { - try { - Thread.sleep(50); - } catch (Exception e) {} - exec[0] = true; - throw new Err(new FailToDoSomething()); - }; - - ag.name = "foo"; - ag.add(fn); - assertThat(ag.hasErr()).isFalse(); - assertThat(exec[0]).isFalse(); - - ag.join(); - assertThat(ag.hasErr()).isTrue(); - assertThat(exec[0]).isTrue(); - - var m = ag.makeErrs(); - assertThat(m).hasSize(1); - switch (m.get("foo").getReason()) { - case FailToDoSomething reason: - break; - default: - fail(m.get("foo").toString()); - break; - } - assertThat(exec[0]).isTrue(); - } - - record Err0() {} - record Err1() {} - - @Test - void should_get_multipleErrors() { - var ag = new AsyncGroupAsync(); - assertThat(ag.hasErr()).isFalse(); - - final boolean[] exec = {false, false, false}; - final Runner fn0 = () -> { - try { - Thread.sleep(200); - } catch (Exception e) {} - exec[0] = true; - throw new Err(new Err0()); - }; - final Runner fn1 = () -> { - try { - Thread.sleep(400); - } catch (Exception e) {} - exec[1] = true; - throw new Err(new Err1()); - }; - final Runner fn2 = () -> { - try { - Thread.sleep(800); - } catch (Exception e) {} - exec[2] = true; - throw new RuntimeException(); - }; - - ag.name = "foo0"; - ag.add(fn0); - ag.name = "foo1"; - ag.add(fn1); - ag.name = "foo2"; - ag.add(fn2); - assertThat(ag.hasErr()).isFalse(); - assertThat(exec[0]).isFalse(); - assertThat(exec[1]).isFalse(); - assertThat(exec[2]).isFalse(); - - ag.join(); - assertThat(ag.hasErr()).isTrue(); - assertThat(exec[0]).isTrue(); - assertThat(exec[1]).isTrue(); - assertThat(exec[2]).isTrue(); - - var m = ag.makeErrs(); - assertThat(m).hasSize(3); - assertThat(m.get("foo0").getReasonName()).isEqualTo("Err0"); - assertThat(m.get("foo1").getReasonName()).isEqualTo("Err1"); - assertThat(m.get("foo2").getReasonName()).isEqualTo("RunnerFailed"); - assertThat(exec[0]).isTrue(); - assertThat(exec[1]).isTrue(); - assertThat(exec[2]).isTrue(); - } -} diff --git a/src/test/java/com/github/sttk/sabi/async/AsyncGroupSyncTest.java b/src/test/java/com/github/sttk/sabi/async/AsyncGroupSyncTest.java deleted file mode 100644 index 1452fce..0000000 --- a/src/test/java/com/github/sttk/sabi/async/AsyncGroupSyncTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.github.sttk.sabi.async; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Nested; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.Runner; - -public class AsyncGroupSyncTest { - - @Test - void should_be_ok() { - var ag = new AsyncGroupSync(); - assertThat(ag.getErr()).isNull(); - - final boolean[] exec = { false }; - final Runner fn = () -> { - exec[0] = true; - }; - - ag.add(fn); - assertThat(ag.getErr()).isNull(); - assertThat(exec[0]).isTrue(); - } - - record FailToDoSomething() {} - - @Test - void should_be_error() { - var ag = new AsyncGroupSync(); - assertThat(ag.getErr()).isNull(); - - final boolean[] exec = { false }; - final Runner fn = () -> { - exec[0] = true; - throw new Err(new FailToDoSomething()); - }; - - ag.add(fn); - switch (ag.getErr().getReason()) { - case FailToDoSomething reason: - break; - default: - fail(ag.getErr().toString()); - break; - } - assertThat(exec[0]).isTrue(); - } - - @Test - void should_be_error_by_runtimeexception() { - var ag = new AsyncGroupSync(); - assertThat(ag.getErr()).isNull(); - - final boolean[] exec = { false }; - final Runner fn = () -> { - exec[0] = true; - throw new RuntimeException(); - }; - - ag.add(fn); - switch (ag.getErr().getReason()) { - case AsyncGroup.RunnerFailed reason: - assertThat(ag.getErr().getCause().getClass()) - .isEqualTo(RuntimeException.class); - break; - default: - fail(ag.getErr().toString()); - break; - } - assertThat(exec[0]).isTrue(); - } -} diff --git a/src/test/java/com/github/sttk/sabi/errs/ErrHandlerTest.java b/src/test/java/com/github/sttk/sabi/errs/ErrHandlerTest.java deleted file mode 100644 index 0d3edd7..0000000 --- a/src/test/java/com/github/sttk/sabi/errs/ErrHandlerTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package errs; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; - -import java.util.List; -import java.util.ArrayList; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.errs.notify.ErrNotifier; - -public class ErrHandlerTest { - - static final List syncLogger = new ArrayList<>(); - static final List asyncLogger = new ArrayList<>(); - - public record FailToDoSomething(String name) {} - - @BeforeEach - void reset() { - try { - var f0 = Err.class.getDeclaredField("notifier"); - f0.setAccessible(true); - var n0 = f0.get(null); - var f1 = ErrNotifier.class.getDeclaredField("isFixed"); - f1.setAccessible(true); - f1.setBoolean(n0, false); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Test - public void should_notify_that_errs_are_created() { - Err.addSyncHandler((err, occ) -> { - syncLogger.add(String.format("1. %s (%s:%d)", - err.getReason().toString(), occ.getFile(), occ.getLine())); - }); - Err.addSyncHandler((err, occ) -> { - syncLogger.add(String.format("2. %s (%s:%d)", - err.getReason().toString(), occ.getFile(), occ.getLine())); - }); - Err.addAsyncHandler((err, occ) -> { - try { Thread.sleep(10); } catch (Exception e) {} - asyncLogger.add(String.format("3. %s (%s:%d)", - err.getReason().toString(), occ.getFile(), occ.getLine())); - }); - Err.addAsyncHandler((err, occ) -> { - try { Thread.sleep(50); } catch (Exception e) {} - asyncLogger.add(String.format("4. %s (%s:%d)", - err.getReason().toString(), occ.getFile(), occ.getLine())); - }); - Err.fixCfg(); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err e) { - assertThat(e.getReason()).isInstanceOf(FailToDoSomething.class); - - try { - Thread.sleep(200); - } catch (Exception e2) {} - - assertThat(syncLogger).hasSize(2); - assertThat(syncLogger.get(0)).startsWith( - "1. FailToDoSomething[name=abc] (ErrHandlerTest.java:58)"); - assertThat(syncLogger.get(1)).startsWith( - "2. FailToDoSomething[name=abc] (ErrHandlerTest.java:58)"); - - assertThat(asyncLogger).hasSize(2); - assertThat(asyncLogger.get(0)).startsWith( - "3. FailToDoSomething[name=abc] (ErrHandlerTest.java:58)"); - assertThat(asyncLogger.get(1)).startsWith( - "4. FailToDoSomething[name=abc] (ErrHandlerTest.java:58)"); - } - } -} diff --git a/src/test/java/com/github/sttk/sabi/errs/ErrTest.java b/src/test/java/com/github/sttk/sabi/errs/ErrTest.java deleted file mode 100644 index 2a8af5b..0000000 --- a/src/test/java/com/github/sttk/sabi/errs/ErrTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.github.sttk.sabi.errs; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import java.io.IOException; - -public class ErrTest { - - // error reasons - record InvalidValue(String value) {} - record FailToGetValue(String name) {} - - enum Reason { - value, - name, - } - - @Test - void should_create_an_Err() { - try { - throw new Err(new InvalidValue("abc")); - } catch (Err e) { - assertThat(e.getMessage()).isEqualTo("{reason=InvalidValue, value=abc}"); - assertThat(e.getReason()).isInstanceOf(InvalidValue.class); - assertThat(e.getReasonName()).isEqualTo("InvalidValue"); - assertThat(e.getReasonPackage()).isEqualTo("com.github.sttk.sabi.errs"); - assertThat(e.get("value")).isEqualTo("abc"); - assertThat(e.get("name")).isNull(); - assertThat(e.get(Reason.value)).isEqualTo("abc"); - assertThat(e.get(Reason.name)).isNull(); - assertThat(e.getCause()).isNull(); - - var m = e.getSituation(); - assertThat(m).hasSize(1); - assertThat(m.get("value")).isEqualTo("abc"); - } - } - - @Test - void should_be_error_if_reason_is_null() { - try { - throw new Err(null); - } catch (Err e) { - fail(e); - } catch (NullPointerException e) { - } catch (Exception e) { - fail(e); - } - - var cause = new IOException(); - try { - throw new Err(null, cause); - } catch (Err e) { - fail(e); - } catch (NullPointerException e) { - } catch (Exception e) { - fail(e); - } - } - - @Test - void should_create_an_Err_with_cause() { - var cause = new IOException(); - try { - throw new Err(new InvalidValue("abc"), cause); - } catch (Err e) { - assertThat(e.getMessage()).isEqualTo("{reason=InvalidValue, value=abc, cause=java.io.IOException}"); - assertThat(e.getReason()).isInstanceOf(InvalidValue.class); - assertThat(e.getReasonName()).isEqualTo("InvalidValue"); - assertThat(e.getReasonPackage()).isEqualTo("com.github.sttk.sabi.errs"); - assertThat(e.get("value")).isEqualTo("abc"); - assertThat(e.get("name")).isNull(); - assertThat(e.get(Reason.value)).isEqualTo("abc"); - assertThat(e.get(Reason.name)).isNull(); - assertThat(e.getCause()).isEqualTo(cause); - - var m = e.getSituation(); - assertThat(m).hasSize(1); - assertThat(e.get("value")).isEqualTo("abc"); - } - } - - @Test - void should_create_an_Err_with_cause_that_is_also_Err() { - var cause = new Err(new FailToGetValue("foo")); - try { - throw new Err(new InvalidValue("abc"), cause); - } catch (Err e) { - assertThat(e.getMessage()).isEqualTo("{reason=InvalidValue, value=abc, cause={reason=FailToGetValue, name=foo}}"); - assertThat(e.getReason()).isInstanceOf(InvalidValue.class); - assertThat(e.getReasonName()).isEqualTo("InvalidValue"); - assertThat(e.getReasonPackage()).isEqualTo("com.github.sttk.sabi.errs"); - assertThat(e.get("value")).isEqualTo("abc"); - assertThat(e.get("name")).isEqualTo("foo"); - assertThat(e.get(Reason.value)).isEqualTo("abc"); - assertThat(e.get(Reason.name)).isEqualTo("foo"); - assertThat(e.getCause()).isEqualTo(cause); - - var m = e.getSituation(); - assertThat(m).hasSize(2); - assertThat(e.get("value")).isEqualTo("abc"); - assertThat(e.get("name")).isEqualTo("foo"); - - assertThat(e.getFileName()).isEqualTo("ErrTest.java"); - assertThat(e.getLineNumber()).isEqualTo(88); - } - } -} diff --git a/src/test/java/com/github/sttk/sabi/errs/notify/ErrNotifierTest.java b/src/test/java/com/github/sttk/sabi/errs/notify/ErrNotifierTest.java deleted file mode 100644 index 6afb753..0000000 --- a/src/test/java/com/github/sttk/sabi/errs/notify/ErrNotifierTest.java +++ /dev/null @@ -1,351 +0,0 @@ -package com.github.sttk.sabi.errs.notify; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.BeforeEach; - -import com.github.sttk.sabi.errs.Err; -import com.github.sttk.sabi.errs.ErrHandler; - -import java.util.ArrayList; - -public class ErrNotifierTest { - - @BeforeEach - void reset() { - try { - var f0 = Err.class.getField("notifier"); - var n0 = f0.get(null); - var f1 = ErrNotifier.class.getField("isFixed"); - f1.setBoolean(n0, false); - } catch (Exception e) {} - } - - @Test - void should_add_handlers_and_fix() { - final var notifier = new ErrNotifier(); - assertThat(notifier.isFixed()).isFalse(); - assertThat(notifier.syncErrHandlers).isEmpty(); - assertThat(notifier.asyncErrHandlers).isEmpty(); - - final ErrHandler h1 = (err, occ) -> {}; - notifier.addSyncHandler(h1); - assertThat(notifier.isFixed()).isFalse(); - assertThat(notifier.syncErrHandlers).containsExactly(h1); - assertThat(notifier.asyncErrHandlers).isEmpty(); - - final ErrHandler h2 = (err, occ) -> {}; - notifier.addSyncHandler(h2); - assertThat(notifier.isFixed()).isFalse(); - assertThat(notifier.syncErrHandlers).containsExactly(h1, h2); - assertThat(notifier.asyncErrHandlers).isEmpty(); - - final ErrHandler h3 = (err, occ) -> {}; - notifier.addAsyncHandler(h3); - assertThat(notifier.isFixed()).isFalse(); - assertThat(notifier.syncErrHandlers).containsExactly(h1, h2); - assertThat(notifier.asyncErrHandlers).containsExactly(h3); - - final ErrHandler h4 = (err, occ) -> {}; - notifier.addAsyncHandler(h4); - assertThat(notifier.isFixed()).isFalse(); - assertThat(notifier.syncErrHandlers).containsExactly(h1, h2); - assertThat(notifier.asyncErrHandlers).containsExactly(h3, h4); - - notifier.fix(); - - final ErrHandler h5 = (err, occ) -> {}; - notifier.addSyncHandler(h5); - assertThat(notifier.isFixed()).isTrue(); - assertThat(notifier.syncErrHandlers).containsExactly(h1, h2); - assertThat(notifier.asyncErrHandlers).containsExactly(h3, h4); - - final ErrHandler h6 = (err, occ) -> {}; - notifier.addAsyncHandler(h6); - assertThat(notifier.isFixed()).isTrue(); - assertThat(notifier.syncErrHandlers).containsExactly(h1, h2); - assertThat(notifier.asyncErrHandlers).containsExactly(h3, h4); - } - - - // error reasons - record FailToDoSomething(String name) {} - - @Nested - public class Notification { - - @Test - void should_do_nothing_when_no_handlers() { - final var notifier = new ErrNotifier(); - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - } catch (Exception e) { - fail(e); - } - } - } - - @Test - void should_execute_sync_handlers() { - final var logs = new ArrayList(); - - final var notifier = new ErrNotifier(); - notifier.addSyncHandler((err, occ) -> { - var log = String.format("%s (%s:%d) %s", err.getReason().toString(), - occ.getFile(), occ.getLine(), occ.getTime().toString()); - logs.add(log); - }); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).isEmpty(); - - notifier.fix(); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).startsWith( - "FailToDoSomething[name=abc] (ErrNotifierTest.java:119) "); - } - - @Test - void should_execute_async_handlers() { - final var logs = new ArrayList(); - - final var notifier = new ErrNotifier(); - notifier.addAsyncHandler((err, occ) -> { - var log = String.format("%s (%s:%d) %s", err.getReason().toString(), - occ.getFile(), occ.getLine(), occ.getTime().toString()); - logs.add(log); - }); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).isEmpty(); - - notifier.fix(); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).startsWith( - "FailToDoSomething[name=abc] (ErrNotifierTest.java:160) "); - } - - @Test - void should_execute_sync_and_async_handlers() { - final var logs = new ArrayList(); - final var notifier = new ErrNotifier(); - notifier.addAsyncHandler((err, occ) -> { - var log = String.format("Async: %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - notifier.addSyncHandler((err, occ) -> { - var log = String.format("Sync: %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).isEmpty(); - - notifier.fix(); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).hasSize(2); - assertThat(logs.get(0)).startsWith( - "Sync: FailToDoSomething[name=abc] (ErrNotifierTest.java:208) "); - assertThat(logs.get(1)).startsWith( - "Async: FailToDoSomething[name=abc] (ErrNotifierTest.java:208) "); - } - - @Test - void should_stop_executing_sync_handlers_if_one_of_handlers_failed() { - final var logs = new ArrayList(); - final var notifier = new ErrNotifier(); - notifier.addAsyncHandler((err, occ) -> { - var log = String.format("Async: %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - notifier.addSyncHandler((err, occ) -> { - var log = String.format("Sync(1): %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - notifier.addSyncHandler((err, occ) -> { - throw new RuntimeException(); - }); - notifier.addSyncHandler((err, occ) -> { - var log = String.format("Sync(3): %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).isEmpty(); - - notifier.fix(); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - fail(); - } catch (RuntimeException e) { - assertThat(e).isNotNull(); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).hasSize(1); - assertThat(logs.get(0)).startsWith( - "Sync(1): FailToDoSomething[name=abc] (ErrNotifierTest.java:267) "); - } - - @Test - void should_execute_all_async_handlers_even_if_one_of_handlers_failed() { - final var logs = new ArrayList(); - final var notifier = new ErrNotifier(); - notifier.addAsyncHandler((err, occ) -> { - try { Thread.sleep(10); } catch (Exception e) {} - var log = String.format("Async(1): %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - notifier.addAsyncHandler((err, occ) -> { - throw new RuntimeException( - "**This exception is not a error but for test purpose.**"); - }); - notifier.addAsyncHandler((err, occ) -> { - try { Thread.sleep(100); } catch (Exception e) {} - var log = String.format("Async(3): %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - notifier.addSyncHandler((err, occ) -> { - var log = String.format("Sync: %s (%s:%d) %s", - err.getReason().toString(), occ.getFile(), occ.getLine(), - occ.getTime().toString()); - logs.add(log); - }); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - assertThat(logs).isEmpty(); - - notifier.fix(); - - try { - throw new Err(new FailToDoSomething("abc")); - } catch (Err err) { - try { - notifier.notify(err); - Thread.sleep(100); - } catch (Exception e) { - fail(e); - } - } - - try { Thread.sleep(200); } catch (Exception e) {} - - assertThat(logs).hasSize(3); - assertThat(logs.get(0)).startsWith( - "Sync: FailToDoSomething[name=abc] (ErrNotifierTest.java:330) "); - assertThat(logs.get(1)).startsWith( - "Async(1): FailToDoSomething[name=abc] (ErrNotifierTest.java:330) "); - assertThat(logs.get(2)).startsWith( - "Async(3): FailToDoSomething[name=abc] (ErrNotifierTest.java:330) "); - } - } -} diff --git a/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java b/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java new file mode 100644 index 0000000..fef45f9 --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java @@ -0,0 +1,155 @@ +package com.github.sttk.sabi.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Nested; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.Runner; +import java.util.HashMap; + +public class AsyncGroupImplTest { + private AsyncGroupImplTest() {} + + @Test + void zero() { + var ag = new AsyncGroupImpl(); + + var m = new HashMap(); + ag.joinAndPutExcsInto(m); + assertThat(m).hasSize(0); + } + + @Test + void ok() { + var ag = new AsyncGroupImpl(); + + boolean[] executed = {false}; + Runner fn = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[0] = true; + }; + + ag.name = "foo"; + ag.add(fn); + assertThat(executed[0]).isFalse(); + + var m = new HashMap(); + ag.joinAndPutExcsInto(m); + assertThat(m).hasSize(0); + assertThat(executed[0]).isTrue(); + } + + @Test + void error() { + var ag = new AsyncGroupImpl(); + + record FailToDoSomething() {} + + boolean[] executed = {false}; + Runner fn = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[0] = true; + throw new Exc(new FailToDoSomething()); + }; + + ag.name = "foo"; + ag.add(fn); + assertThat(executed[0]).isFalse(); + + var m = new HashMap(); + ag.joinAndPutExcsInto(m); + assertThat(m).hasSize(1); + assertThat(executed[0]).isTrue(); + + switch (m.get("foo").getReason()) { + case FailToDoSomething r -> {} + default -> fail(); + } + } + + @Test + void multiple_errors_with_an_error_map() { + var ag = new AsyncGroupImpl(); + + record Reason0() {} + record Reason1() {} + record Reason2() {} + + boolean[] executed = {false, false, false}; + + Runner fn0 = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[0] = true; + throw new Exc(new Reason0()); + }; + Runner fn1 = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[1] = true; + throw new Exc(new Reason1()); + }; + Runner fn2 = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[2] = true; + throw new Exc(new Reason2()); + }; + + ag.name = "foo0"; + ag.add(fn0); + ag.name = "foo1"; + ag.add(fn1); + ag.name = "foo2"; + ag.add(fn2); + + var m = new HashMap(); + ag.joinAndPutExcsInto(m); + assertThat(m).hasSize(3); + assertThat(executed[0]).isTrue(); + assertThat(executed[1]).isTrue(); + assertThat(executed[2]).isTrue(); + + assertThat(m.get("foo0").toString()).isEqualTo("com.github.sttk.errs.Exc { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason0 Reason0[], file = AsyncGroupImplTest.java, line = 85 }"); + assertThat(m.get("foo1").toString()).isEqualTo("com.github.sttk.errs.Exc { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason1 Reason1[], file = AsyncGroupImplTest.java, line = 90 }"); + assertThat(m.get("foo2").toString()).isEqualTo("com.github.sttk.errs.Exc { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason2 Reason2[], file = AsyncGroupImplTest.java, line = 95 }"); + } + + @Test + void multiple_errors_without_an_error_map() { + var ag = new AsyncGroupImpl(); + + record Reason0() {} + record Reason1() {} + record Reason2() {} + + boolean[] executed = {false, false, false}; + + Runner fn0 = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[0] = true; + throw new Exc(new Reason0()); + }; + Runner fn1 = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[1] = true; + throw new Exc(new Reason1()); + }; + Runner fn2 = () -> { + try { Thread.sleep(50); } catch (Exception e) {} + executed[2] = true; + throw new Exc(new Reason2()); + }; + + ag.name = "foo0"; + ag.add(fn0); + ag.name = "foo1"; + ag.add(fn1); + ag.name = "foo2"; + ag.add(fn2); + + ag.joinAndIgnoreExcs(); + assertThat(executed[0]).isTrue(); + assertThat(executed[1]).isTrue(); + assertThat(executed[2]).isTrue(); + } +} diff --git a/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java b/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java new file mode 100644 index 0000000..9806dc8 --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java @@ -0,0 +1,649 @@ +package com.github.sttk.sabi.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.DataSrc; +import com.github.sttk.sabi.DataAcc; +import com.github.sttk.sabi.DataHub; +import com.github.sttk.sabi.Sabi; +import com.github.sttk.sabi.Logic; +import java.util.List; +import java.util.ArrayList; + +public class DataAccTest { + DataAccTest() {} + + final void suppressWarnings_unused(Object a) {} + + static class FooDataSrc implements DataSrc { + private int id; + private String text; + private List logger; + private boolean willFail; + + FooDataSrc(int id, String text, List logger, boolean willFail) { + this.id = id; + this.text = text; + this.logger = logger; + this.willFail = willFail; + } + @Override + public void setup(AsyncGroup ag) throws Exc { + if (this.willFail) { + this.logger.add(String.format("FooDataSrc %d failed to setup", this.id)); + throw new Exc("XXX"); + } + this.logger.add(String.format("FooDataSrc %d setupped", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("FooDataSrc %d closed", this.id)); + } + @Override + public DataConn createDataConn() throws Exc { + this.logger.add(String.format("FooDataSrc %d created FooDataConn", this.id)); + return new FooDataConn(this.id, this.text, this.logger); + } + } + + static class FooDataConn implements DataConn { + private int id; + private String text; + private boolean committed; + private List logger; + + FooDataConn(int id, String text, List logger) { + this.id = id; + this.text = text; + this.committed = committed; + this.logger = logger; + } + String getText() { + return this.text; + } + @Override + public void commit(AsyncGroup ag) throws Exc { + this.committed = true; + this.logger.add(String.format("FooDataConn %d committed", this.id)); + } + @Override + public void preCommit(AsyncGroup ag) throws Exc { + this.logger.add(String.format("FooDataConn %d pre committed", this.id)); + } + @Override + public void postCommit(AsyncGroup ag) { + this.logger.add(String.format("FooDataConn %d post committed", this.id)); + } + @Override + public boolean shouldForceBack() { + return this.committed; + } + @Override + public void rollback(AsyncGroup ag) { + this.logger.add(String.format("FooDataConn %d rollbacked", this.id)); + } + @Override + public void forceBack(AsyncGroup ag) { + this.logger.add(String.format("FooDataConn %d forced back", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("FooDataConn %d closed", this.id)); + } + } + + static class BarDataSrc implements DataSrc { + private int id; + private String text; + private List logger; + private boolean willFail; + + BarDataSrc(int id, List logger, boolean willFail) { + this.id = id; + this.text = null; + this.logger = logger; + this.willFail = willFail; + } + @Override + public void setup(AsyncGroup ag) throws Exc { + if (this.willFail) { + this.logger.add(String.format("BarDataSrc %d failed to setup", this.id)); + throw new Exc("XXX"); + } + this.logger.add(String.format("BarDataSrc %d setupped", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("BarDataSrc.text = %s", this.text)); + this.logger.add(String.format("BarDataSrc %d closed", this.id)); + } + @Override + public DataConn createDataConn() throws Exc { + this.logger.add(String.format("BarDataSrc %d created BarDataConn", this.id)); + return new BarDataConn(this.id, this.text, this.logger, this); + } + } + + static class BarDataConn implements DataConn { + private int id; + private String text; + private boolean committed; + private List logger; + private BarDataSrc ds; + + BarDataConn(int id, String text, List logger, BarDataSrc ds) { + this.id = id; + this.text = text; + this.committed = false; + this.logger = logger; + this.ds = ds; + } + void setText(String s) { + this.text = s; + } + @Override + public void commit(AsyncGroup ag) throws Exc { + this.committed = true; + this.ds.text = this.text; + this.logger.add(String.format("BarDataConn %d committed", this.id)); + } + @Override + public void preCommit(AsyncGroup ag) throws Exc { + this.logger.add(String.format("BarDataConn %d pre committed", this.id)); + } + @Override + public void postCommit(AsyncGroup ag) { + this.logger.add(String.format("BarDataConn %d post committed", this.id)); + } + @Override + public boolean shouldForceBack() { + return this.committed; + } + @Override + public void rollback(AsyncGroup ag) { + this.logger.add(String.format("BarDataConn %d rollbacked", this.id)); + } + @Override + public void forceBack(AsyncGroup ag) { + this.logger.add(String.format("BarDataConn %d forced back", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("BarDataConn.text = %s", this.text)); + this.logger.add(String.format("BarDataConn %d closed", this.id)); + } + } + + /// + + static interface SampleData { + String getValue() throws Exc; + void setValue(String text) throws Exc; + } + + static class SampleLogic implements Logic { + @Override + public void run(SampleData data) throws Exc { + var v = data.getValue(); + data.setValue(v); + } + } + + static class FailingLogic implements Logic { + @Override + public void run(SampleData data) throws Exc { + throw new Exc("ZZZ"); + } + } + + static interface AllLogicData extends SampleData {} + + static interface FooDataAcc extends DataAcc, AllLogicData { + @Override + default String getValue() throws Exc { + var conn = getDataConn("foo", FooDataConn.class); + return conn.getText(); + } + } + + static interface BarDataAcc extends DataAcc, AllLogicData { + @Override + default void setValue(String text) throws Exc { + var conn = getDataConn("bar", BarDataConn.class); + conn.setText(text); + } + } + + /// + + static class SampleDataHub extends DataHub implements FooDataAcc, BarDataAcc {} + + /// + + @Nested + class TestLogicArgument { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + Sabi.uses("foo", new FooDataSrc(1, "hello", logger, false)); + Sabi.uses("bar", new BarDataSrc(2, logger, false)); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + new SampleLogic().run(hub); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 setupped", + "BarDataSrc 2 setupped", + "FooDataSrc 1 created FooDataConn", + "BarDataSrc 2 created BarDataConn", + "BarDataConn.text = hello", + "BarDataConn 2 closed", + "FooDataConn 1 closed", + "BarDataSrc.text = null", + "BarDataSrc 2 closed", + "FooDataSrc 1 closed" + ); + } + } + + @Nested + class TestDataHubRunUsingGlobal { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + Sabi.uses("foo", new FooDataSrc(1, "hello", logger, false)); + Sabi.uses("bar", new BarDataSrc(2, logger, false)); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.run(new SampleLogic()); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 setupped", + "BarDataSrc 2 setupped", + "FooDataSrc 1 created FooDataConn", + "BarDataSrc 2 created BarDataConn", + "BarDataConn.text = hello", + "BarDataConn 2 closed", + "FooDataConn 1 closed", + "BarDataSrc.text = null", + "BarDataSrc 2 closed", + "FooDataSrc 1 closed" + ); + } + } + + @Nested + class TestDataHubRunUsingLocal { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(1, "hello", logger, false)); + hub.uses("bar", new BarDataSrc(2, logger, false)); + + hub.run(new SampleLogic()); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 setupped", + "BarDataSrc 2 setupped", + "FooDataSrc 1 created FooDataConn", + "BarDataSrc 2 created BarDataConn", + "BarDataConn.text = hello", + "BarDataConn 2 closed", + "FooDataConn 1 closed", + "BarDataSrc.text = null", + "BarDataSrc 2 closed", + "FooDataSrc 1 closed" + ); + } + + @Test + void test_not_run_logic_if_fail_to_setup_local_data_src() { + var logger = new ArrayList(); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(1, "hello", logger, true)); + hub.uses("bar", new BarDataSrc(2, logger, false)); + + hub.run(new SampleLogic()); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToSetupLocalDataSrcs r -> { + var e2 = r.errors().get("foo"); + assertThat(e2.getReason()).isEqualTo("XXX"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 failed to setup" + ); + } + } + + @Nested + class TestDataHubRunUsingGlobalAndLocal { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + Sabi.uses("bar", new BarDataSrc(1, logger, false)); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(2, "Hello", logger, false)); + + hub.run(new SampleLogic()); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "BarDataSrc 1 setupped", + "FooDataSrc 2 setupped", + "FooDataSrc 2 created FooDataConn", + "BarDataSrc 1 created BarDataConn", + "BarDataConn.text = Hello", + "BarDataConn 1 closed", + "FooDataConn 2 closed", + "FooDataSrc 2 closed", + "BarDataSrc.text = null", + "BarDataSrc 1 closed" + ); + } + } + + @Nested + class TestDataHubTxnUsingGlobal { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + Sabi.uses("foo", new FooDataSrc(1, "Hello", logger, false)); + Sabi.uses("bar", new BarDataSrc(2, logger, false)); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.txn(new SampleLogic()); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 setupped", + "BarDataSrc 2 setupped", + "FooDataSrc 1 created FooDataConn", + "BarDataSrc 2 created BarDataConn", + "FooDataConn 1 pre committed", + "BarDataConn 2 pre committed", + "FooDataConn 1 committed", + "BarDataConn 2 committed", + "FooDataConn 1 post committed", + "BarDataConn 2 post committed", + "BarDataConn.text = Hello", + "BarDataConn 2 closed", + "FooDataConn 1 closed", + "BarDataSrc.text = Hello", + "BarDataSrc 2 closed", + "FooDataSrc 1 closed" + ); + } + } + + @Nested + class TestDataHubTxnUsingLocal { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(1, "Hello", logger, false)); + hub.uses("bar", new BarDataSrc(2, logger, false)); + + hub.txn(new SampleLogic()); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 setupped", + "BarDataSrc 2 setupped", + "FooDataSrc 1 created FooDataConn", + "BarDataSrc 2 created BarDataConn", + "FooDataConn 1 pre committed", + "BarDataConn 2 pre committed", + "FooDataConn 1 committed", + "BarDataConn 2 committed", + "FooDataConn 1 post committed", + "BarDataConn 2 post committed", + "BarDataConn.text = Hello", + "BarDataConn 2 closed", + "FooDataConn 1 closed", + "BarDataSrc.text = Hello", + "BarDataSrc 2 closed", + "FooDataSrc 1 closed" + ); + } + + @Test + void test_not_run_logic_if_fail_to_setup_local_data_src() { + var logger = new ArrayList(); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(1, "Hello", logger, true)); + hub.uses("bar", new BarDataSrc(2, logger, false)); + + hub.txn(new SampleLogic()); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToSetupLocalDataSrcs r -> { + var e2 = r.errors().get("foo"); + assertThat(e2.getReason()).isEqualTo("XXX"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 failed to setup" + ); + } + + @Test + void test_not_run_logic_in_txn_and_rollback() { + var logger = new ArrayList(); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(1, "Hello", logger, false)); + hub.uses("bar", new BarDataSrc(2, logger, false)); + + hub.txn(new FailingLogic()); + } catch (Exc e) { + assertThat(e.getReason()).isEqualTo("ZZZ"); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "FooDataSrc 1 setupped", + "BarDataSrc 2 setupped", + "BarDataSrc.text = null", + "BarDataSrc 2 closed", + "FooDataSrc 1 closed" + ); + } + } + + @Nested + class TestDataHubTxnUsingGlobalAndLocal { + @BeforeEach + void beforeEach() { + DataHubInnerTest.resetGlobalVariables(); + } + @AfterEach + void afterEach() { + DataHubInnerTest.resetGlobalVariables(); + } + + @Test + void test() { + var logger = new ArrayList(); + + Sabi.uses("bar", new BarDataSrc(1, logger, false)); + + try (var ac = Sabi.setup()) { + suppressWarnings_unused(ac); + try (var hub = new SampleDataHub()) { + hub.uses("foo", new FooDataSrc(2, "Hello", logger, false)); + + hub.txn(new SampleLogic()); + } catch (Exception e) { + fail(e); + } + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "BarDataSrc 1 setupped", + "FooDataSrc 2 setupped", + "FooDataSrc 2 created FooDataConn", + "BarDataSrc 1 created BarDataConn", + "FooDataConn 2 pre committed", + "BarDataConn 1 pre committed", + "FooDataConn 2 committed", + "BarDataConn 1 committed", + "FooDataConn 2 post committed", + "BarDataConn 1 post committed", + "BarDataConn.text = Hello", + "BarDataConn 1 closed", + "FooDataConn 2 closed", + "FooDataSrc 2 closed", + "BarDataSrc.text = Hello", + "BarDataSrc 1 closed" + ); + } + } +} diff --git a/src/test/java/com/github/sttk/sabi/internal/DataConnListTest.java b/src/test/java/com/github/sttk/sabi/internal/DataConnListTest.java new file mode 100644 index 0000000..d13f52d --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/DataConnListTest.java @@ -0,0 +1,88 @@ +package com.github.sttk.sabi.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Nested; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import java.util.List; +import java.util.ArrayList; + +public class DataConnListTest { + private DataConnListTest() {} + + static class SampleDataConn implements DataConn { + private int id; + private List logger; + + public SampleDataConn(int id, List logger) { this.id = id; this.logger = logger; } + public void commit(AsyncGroup ag) throws Exc {} + public void preCommit(AsyncGroup ag) throws Exc {} + public void postCommit(AsyncGroup ag) {} + public boolean shouldForceBack() { return false; } + public void rollback(AsyncGroup ag) {} + public void forceBack(AsyncGroup ag) {} + public void close() { + this.logger.add(String.format("SampleDataConn %d closed", this.id)); + } + } + + @Test + void test_new() { + var dcList = new DataConnList(); + + assertThat(dcList.head).isNull(); + assertThat(dcList.last).isNull(); + } + + @Test + void test_appendContainer() { + var dcList = new DataConnList(); + var logger = new ArrayList(); + + var dc1 = new SampleDataConn(1, logger); + var cont1 = new DataConnContainer("foo", dc1); + dcList.appendContainer(cont1); + + assertThat(dcList.head).isEqualTo(cont1); + assertThat(dcList.last).isEqualTo(cont1); + + var dc2 = new SampleDataConn(2, logger); + var cont2 = new DataConnContainer("bar", dc2); + dcList.appendContainer(cont2); + + assertThat(dcList.head).isEqualTo(cont1); + assertThat(dcList.last).isEqualTo(cont2); + assertThat(cont1.prev).isNull(); + assertThat(cont1.next).isEqualTo(cont2); + assertThat(cont2.prev).isEqualTo(cont1); + assertThat(cont2.next).isNull(); + + var dc3 = new SampleDataConn(3, logger); + var cont3 = new DataConnContainer("baz", dc3); + dcList.appendContainer(cont3); + + assertThat(dcList.head).isEqualTo(cont1); + assertThat(dcList.last).isEqualTo(cont3); + assertThat(cont1.prev).isNull(); + assertThat(cont1.next).isEqualTo(cont2); + assertThat(cont2.prev).isEqualTo(cont1); + assertThat(cont2.next).isEqualTo(cont3); + assertThat(cont3.prev).isEqualTo(cont2); + assertThat(cont3.next).isNull(); + + dcList.closeDataConns(); + + assertThat(dcList.head).isNull(); + assertThat(dcList.last).isNull(); + + assertThat(logger).containsExactly( + "SampleDataConn 3 closed", + "SampleDataConn 2 closed", + "SampleDataConn 1 closed" + ); + } +} diff --git a/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java b/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java new file mode 100644 index 0000000..aba0b71 --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java @@ -0,0 +1,2173 @@ +package com.github.sttk.sabi.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.DataSrc; +import com.github.sttk.sabi.DataHub; +import java.util.List; +import java.util.ArrayList; + +public class DataHubInnerTest { + private DataHubInnerTest() {} + + final static int FAIL__NOT = 0; + final static int FAIL__SETUP = 1; + final static int FAIL__CREATE_DATA_CONN = 2; + final static int FAIL__COMMIT = 3; + final static int FAIL__PRE_COMMIT = 4; + + final void suppressWarnings_unused(Object a) {} + + static class SyncDataSrc implements DataSrc { + private int id; + private int fail; + private List logger; + + SyncDataSrc(int id, int fail, List logger) { + this.id = id; + this.fail = fail; + this.logger = logger; + } + @Override + public void setup(AsyncGroup ag) throws Exc { + if (this.fail == FAIL__SETUP) { + this.logger.add(String.format("SyncDataSrc %d failed to setup", this.id)); + throw new Exc("XXX"); + } + this.logger.add(String.format("SyncDataSrc %d setupped", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("SyncDataSrc %d closed", this.id)); + } + @Override + public DataConn createDataConn() throws Exc { + if (this.fail == FAIL__CREATE_DATA_CONN) { + this.logger.add(String.format("SyncDataSrc %d failed to create a DataConn", this.id)); + throw new Exc("xxx"); + } + this.logger.add(String.format("SyncDataSrc %d created DataConn", this.id)); + var conn = new SyncDataConn(this.id, this.fail, this.logger); + return conn; + } + } + + static class AsyncDataSrc implements DataSrc { + private int id; + private int fail; + private List logger; + + AsyncDataSrc(int id, int fail, List logger) { + this.id = id; + this.fail = fail; + this.logger = logger; + } + @Override + public void setup(AsyncGroup ag) throws Exc { + ag.add(() -> { + try { Thread.sleep(50); } catch (Exception e) {} + if (this.fail == FAIL__SETUP) { + this.logger.add(String.format("AsyncDataSrc %d failed to setup", this.id)); + throw new Exc("YYY"); + } + this.logger.add(String.format("AsyncDataSrc %d setupped", this.id)); + }); + } + @Override + public void close() { + this.logger.add(String.format("AsyncDataSrc %d closed", this.id)); + } + @Override + public DataConn createDataConn() throws Exc { + if (this.fail == FAIL__CREATE_DATA_CONN) { + this.logger.add(String.format("AsyncDataSrc %d failed to create a DataConn", this.id)); + throw new Exc("yyy"); + } + this.logger.add(String.format("AsyncDataSrc %d created DataConn", this.id)); + var conn = new AsyncDataConn(this.id, this.fail, this.logger); + return conn; + } + } + + static class SyncDataConn implements DataConn { + private int id; + private int fail; + private boolean committed; + private List logger; + + SyncDataConn(int id, int fail, List logger) { + this.id = id; + this.fail = fail; + this.logger = logger; + } + @Override + public void commit(AsyncGroup ag) throws Exc { + if (this.fail == FAIL__COMMIT) { + this.logger.add(String.format("SyncDataConn %d failed to commit", this.id)); + throw new Exc("ZZZ"); + } + this.committed = true; + this.logger.add(String.format("SyncDataConn %d committed", this.id)); + } + @Override + public void preCommit(AsyncGroup ag) throws Exc { + if (this.fail == FAIL__PRE_COMMIT) { + this.logger.add(String.format("SyncDataConn %d failed to pre commit", this.id)); + throw new Exc("zzz"); + } + this.logger.add(String.format("SyncDataConn %d pre committed", this.id)); + } + @Override + public void postCommit(AsyncGroup ag) { + this.logger.add(String.format("SyncDataConn %d post committed", this.id)); + } + @Override + public boolean shouldForceBack() { + return this.committed; + } + @Override + public void rollback(AsyncGroup ag) { + this.logger.add(String.format("SyncDataConn %d rollbacked", this.id)); + } + @Override + public void forceBack(AsyncGroup ag) { + this.logger.add(String.format("SyncDataConn %d forced back", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("SyncDataConn %d closed", this.id)); + } + } + + static class AsyncDataConn implements DataConn { + private int id; + private int fail; + private boolean committed; + private List logger; + + AsyncDataConn(int id, int fail, List logger) { + this.id = id; + this.fail = fail; + this.logger = logger; + } + @Override + public void commit(AsyncGroup ag) throws Exc { + if (this.fail == FAIL__COMMIT) { + this.logger.add(String.format("AsyncDataConn %d failed to commit", this.id)); + throw new Exc("VVV"); + } + this.committed = true; + this.logger.add(String.format("AsyncDataConn %d committed", this.id)); + } + @Override + public void preCommit(AsyncGroup ag) throws Exc { + if (this.fail == FAIL__PRE_COMMIT) { + this.logger.add(String.format("AsyncDataConn %d failed to pre commit", this.id)); + throw new Exc("vvv"); + } + this.logger.add(String.format("AsyncDataConn %d pre committed", this.id)); + } + @Override + public void postCommit(AsyncGroup ag) { + this.logger.add(String.format("AsyncDataConn %d post committed", this.id)); + } + @Override + public boolean shouldForceBack() { + return this.committed; + } + @Override + public void rollback(AsyncGroup ag) { + this.logger.add(String.format("AsyncDataConn %d rollbacked", this.id)); + } + @Override + public void forceBack(AsyncGroup ag) { + this.logger.add(String.format("AsyncDataConn %d forced back", this.id)); + } + @Override + public void close() { + this.logger.add(String.format("AsyncDataConn %d closed", this.id)); + } + } + + static void resetGlobalVariables() { + DataHubInner.GLOBAL_DATA_SRCS_FIXED.set(false); + DataHubInner.GLOBAL_DATA_SRC_LIST.closeDataSrcs(); + } + + @Nested + class TestOfGlobalFunctions { + @BeforeEach + void beforeEach() { + resetGlobalVariables(); + } + @AfterEach + void afterEach() { + resetGlobalVariables(); + } + + @Test + void setup_and_shutdown() { + var logger = new ArrayList(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + var ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("bar"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("bar"); + ptr = ptr.next; + assertThat(ptr).isNull(); + } catch (Exception e) { + fail(e); + } + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void fail_to_setup() { + var logger = new ArrayList(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__SETUP, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__SETUP, logger)); + + var ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("bar"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToSetupGlobalDataSrcs r -> { + assertThat(r.errors()).hasSize(2); + assertThat(r.errors().get("foo").getReason()).isEqualTo("YYY"); + assertThat(r.errors().get("bar").getReason()).isEqualTo("XXX"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 failed to setup", + "AsyncDataSrc 1 failed to setup" + ); + } + + @Test + void cannot_add_global_data_srcs_after_setup() { + var logger = new ArrayList(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + + var ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + } catch (Exception e) { + fail(e); + } + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + assertThat(logger).containsExactly( + "AsyncDataSrc 1 setupped", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void do_nothing_if_executing_setup_twice() { + var logger = new ArrayList(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + + var ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + } catch (Exception e) { + fail(e); + } + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + assertThat(logger).containsExactly( + "AsyncDataSrc 1 setupped", + "AsyncDataSrc 1 closed" + ); + } + } + + @Nested + class TestOfDataHubLocal { + @BeforeEach + void beforeEach() { + resetGlobalVariables(); + } + @AfterEach + void afterEach() { + resetGlobalVariables(); + } + + @Test + void new_and_close_with_no_global_data_srcs() { + var hub = new DataHubInner(); + + assertThat(hub.localDataSrcList.notSetupHead).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(0); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.close(); + } + + @Test + void new_and_close_with_global_data_srcs() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + var ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("bar"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + var hub = new DataHubInner(); + + assertThat(hub.localDataSrcList.notSetupHead).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + + ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("bar"); + ptr = ptr.next; + assertThat(ptr).isNull(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void uses_and_disuses() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + assertThat(hub.localDataSrcList.notSetupHead).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.uses("baz", new SyncDataSrc(3, FAIL__NOT, logger)); + var ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.uses("qux", new AsyncDataSrc(4, FAIL__NOT, logger)); + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.disuses("foo"); // do nothing because of global + hub.disuses("bar"); // do nothing because of global + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.disuses("baz"); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.disuses("qux"); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 3 closed", + "AsyncDataSrc 4 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void cannot_add_and_remove_data_src_between_begin_and_end() { + var logger = new ArrayList(); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + assertThat(hub.localDataSrcList.notSetupHead).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(0); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.uses("baz", new SyncDataSrc(1, FAIL__NOT, logger)); + var ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.localDataSrcList.didSetupHead).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(0); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + try { + hub.begin(); + } catch (Exception e) { + fail(e); + } + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isTrue(); + + hub.uses("foo", new AsyncDataSrc(2, FAIL__NOT, logger)); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isTrue(); + + hub.disuses("baz"); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.uses("foo", new AsyncDataSrc(2, FAIL__NOT, logger)); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.disuses("baz"); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("foo"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNull(); + assertThat(hub.dataConnList.head).isNull(); + assertThat(hub.dataSrcMap).hasSize(0); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 1 setupped", + "SyncDataSrc 1 closed" + ); + } + + @Test + void begin_and_end() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new SyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new AsyncDataSrc(4, FAIL__NOT, logger)); + + var ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + try { + hub.begin(); + } catch (Exception e) { + fail(e); + } + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(4); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(4); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 3 setupped", + "AsyncDataSrc 4 setupped", + "AsyncDataSrc 4 closed", + "SyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void begin_and_end_but_fail_sync() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__SETUP, logger)); + + try { + hub.begin(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToSetupLocalDataSrcs rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("qux"); + assertThat(e2.getReason()).isEqualTo("XXX"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + var ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(3); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(3); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 failed to setup", + "AsyncDataSrc 3 setupped", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void begin_and_end_but_fail_async() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__SETUP, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + try { + hub.begin(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToSetupLocalDataSrcs rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("baz"); + assertThat(e2.getReason()).isEqualTo("YYY"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + var ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(3); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + ptr = hub.localDataSrcList.notSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("baz"); + ptr = ptr.next; + assertThat(ptr).isNull(); + ptr = hub.localDataSrcList.didSetupHead; + assertThat(ptr).isNotNull(); + assertThat(ptr.name).isEqualTo("qux"); + ptr = ptr.next; + assertThat(ptr).isNull(); + assertThat(hub.dataSrcMap).hasSize(3); + assertThat(hub.dataConnMap).hasSize(0); + assertThat(hub.fixed).isFalse(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 failed to setup", + "SyncDataSrc 4 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void commit() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + try { + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + /// + + conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + hub.commit(); + } catch (Exception e) { + fail(e); + } + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 pre committed", + + "AsyncDataConn 1 committed", + "SyncDataConn 2 committed", + "AsyncDataConn 3 committed", + "SyncDataConn 4 committed", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void fail_to_cast_new_data_conn() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try { + hub.begin(); + } catch (Exception e) { + fail(e); + } + + try { + hub.getDataConn("foo", SyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCastDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("foo"); + assertThat(rsn.castToType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$SyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + try { + hub.getDataConn("bar", AsyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCastDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("bar"); + assertThat(rsn.castToType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$AsyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "AsyncDataSrc 1 setupped", + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void fail_to_cast_reused_data_conn() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try { + hub.begin(); + } catch (Exception e) { + fail(e); + } + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isInstanceOf(AsyncDataConn.class); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isInstanceOf(SyncDataConn.class); + + try { + hub.getDataConn("foo", SyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCastDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("foo"); + assertThat(rsn.castToType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$SyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + try { + hub.getDataConn("bar", AsyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCastDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("bar"); + assertThat(rsn.castToType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$AsyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "AsyncDataSrc 1 setupped", + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void fail_to_create_data_conn() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("bar", new SyncDataSrc(2, FAIL__CREATE_DATA_CONN, logger)); + + try { + hub.begin(); + } catch (Exception e) { + fail(e); + } + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isInstanceOf(AsyncDataConn.class); + + try { + hub.getDataConn("bar", AsyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCreateDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("bar"); + assertThat(rsn.dataConnType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$AsyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "AsyncDataSrc 1 setupped", + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 failed to create a DataConn", + "AsyncDataConn 1 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void fail_to_create_data_conn_because_of_no_data_src() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try { + hub.begin(); + } catch (Exception e) { + fail(e); + } + + try { + hub.getDataConn("baz", SyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.NoDataSrcToCreateDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("baz"); + assertThat(rsn.dataConnType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$SyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + try { + hub.getDataConn("qux", AsyncDataConn.class); + fail(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.NoDataSrcToCreateDataConn rsn -> { + assertThat(rsn.name()).isEqualTo("qux"); + assertThat(rsn.dataConnType()) + .isEqualTo("com.github.sttk.sabi.internal.DataHubInnerTest$AsyncDataConn"); + } + default -> fail(e); + } + } catch (Exception e) { + fail(e); + } + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "AsyncDataSrc 1 setupped", + "SyncDataSrc 2 setupped", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void commit_when_no_data_conn() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + try { + hub.begin(); + hub.commit(); + hub.end(); + } catch (Exception e) { + fail(e); + } + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void commit_but_fail_global_sync() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__COMMIT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("bar"); + assertThat(e2.getReason()).isEqualTo("ZZZ"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 pre committed", + + "AsyncDataConn 1 committed", + "SyncDataConn 2 failed to commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void commit_but_fail_global_async() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__COMMIT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("foo"); + assertThat(e2.getReason()).isEqualTo("VVV"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 pre committed", + + "AsyncDataConn 1 failed to commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void commit_but_fail_local_sync() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__COMMIT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("qux"); + assertThat(e2.getReason()).isEqualTo("ZZZ"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 pre committed", + + "AsyncDataConn 1 committed", + "SyncDataConn 2 committed", + "AsyncDataConn 3 committed", + "SyncDataConn 4 failed to commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void commit_but_fail_local_async() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__COMMIT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("baz"); + assertThat(e2.getReason()).isEqualTo("VVV"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 pre committed", + + "AsyncDataConn 1 committed", + "SyncDataConn 2 committed", + "AsyncDataConn 3 failed to commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void pre_commit_but_fail_global_sync() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__PRE_COMMIT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("bar"); + assertThat(e2.getReason()).isEqualTo("zzz"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 failed to pre commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void pre_commit_but_fail_global_async() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__PRE_COMMIT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("bar"); + assertThat(e2.getReason()).isEqualTo("zzz"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 failed to pre commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void pre_commit_but_fail_local_sync() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__PRE_COMMIT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("qux"); + assertThat(e2.getReason()).isEqualTo("zzz"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 failed to pre commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void pre_commit_but_fail_local_async() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__PRE_COMMIT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + try { + hub.commit(); + } catch (Exc e) { + switch (e.getReason()) { + case DataHub.FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var e2 = rsn.errors().get("baz"); + assertThat(e2.getReason()).isEqualTo("vvv"); + } + default -> fail(); + } + } catch (Exception e) { + fail(e); + } + + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 failed to pre commit", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void rollback() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + hub.rollback(); + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 rollbacked", + "SyncDataConn 2 rollbacked", + "AsyncDataConn 3 rollbacked", + "SyncDataConn 4 rollbacked", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void force_back() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + hub.commit(); + hub.rollback(); + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 pre committed", + "SyncDataConn 2 pre committed", + "AsyncDataConn 3 pre committed", + "SyncDataConn 4 pre committed", + + "AsyncDataConn 1 committed", + "SyncDataConn 2 committed", + "AsyncDataConn 3 committed", + "SyncDataConn 4 committed", + + "AsyncDataConn 1 forced back", + "SyncDataConn 2 forced back", + "AsyncDataConn 3 forced back", + "SyncDataConn 4 forced back", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void post_commit() { + var logger = new ArrayList(); + + DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + + try (var ac = DataHubInner.setupGlobals()) { + suppressWarnings_unused(ac); + var hub = new DataHubInner(); + + hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); + hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + + hub.begin(); + + var conn1 = hub.getDataConn("foo", AsyncDataConn.class); + assertThat(conn1).isNotNull(); + + var conn2 = hub.getDataConn("bar", SyncDataConn.class); + assertThat(conn2).isNotNull(); + + var conn3 = hub.getDataConn("baz", AsyncDataConn.class); + assertThat(conn3).isNotNull(); + + var conn4 = hub.getDataConn("qux", SyncDataConn.class); + assertThat(conn4).isNotNull(); + + hub.postCommit(); + hub.end(); + + hub.close(); + } catch (Exception e) { + fail(e); + } + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "SyncDataSrc 4 setupped", + "AsyncDataSrc 3 setupped", + + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "AsyncDataSrc 3 created DataConn", + "SyncDataSrc 4 created DataConn", + + "AsyncDataConn 1 post committed", + "SyncDataConn 2 post committed", + "AsyncDataConn 3 post committed", + "SyncDataConn 4 post committed", + + "SyncDataConn 4 closed", + "AsyncDataConn 3 closed", + "SyncDataConn 2 closed", + "AsyncDataConn 1 closed", + + "SyncDataSrc 4 closed", + "AsyncDataSrc 3 closed", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + } +} diff --git a/src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java b/src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java new file mode 100644 index 0000000..58cbdbc --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java @@ -0,0 +1,972 @@ +package com.github.sttk.sabi.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Nested; + +import com.github.sttk.errs.Exc; +import com.github.sttk.sabi.Runner; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.DataSrc; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; + +public class DataSrcListTest { + private DataSrcListTest() {} + + static class SyncDataSrc implements DataSrc { + int id; + boolean willFail; + List logger; + + SyncDataSrc(int id, List logger, boolean willFail) { + this.id = id; + this.willFail = willFail; + this.logger = logger; + } + + @Override + public void setup(AsyncGroup ag) throws Exc { + if (this.willFail) { + logger.add(String.format("SyncDataSrc %d failed to setup", this.id)); + throw new Exc("XXX"); + } + logger.add(String.format("SyncDataSrc %d setupped", this.id)); + } + + @Override + public void close() { + logger.add(String.format("SyncDataSrc %d closed", this.id)); + } + + @Override + public DataConn createDataConn() throws Exc { + logger.add(String.format("SyncDataSrc %d created DataConn", this.id)); + var conn = new SyncDataConn(); + return conn; + } + } + + static class AsyncDataSrc implements DataSrc { + int id; + boolean willFail; + List logger; + + AsyncDataSrc(int id, List logger, boolean willFail) { + this.id = id; + this.willFail = willFail; + this.logger = logger; + } + + @Override + public void setup(AsyncGroup ag) throws Exc { + ag.add(() -> { + try { Thread.sleep(50); } catch (Exception e) {} + if (this.willFail) { + logger.add(String.format("AsyncDataSrc %d failed to setup", this.id)); + throw new Exc("XXX"); + } + logger.add(String.format("AsyncDataSrc %d setupped", this.id)); + }); + } + + @Override + public void close() { + logger.add(String.format("AsyncDataSrc %d closed", this.id)); + } + + @Override + public DataConn createDataConn() throws Exc { + logger.add(String.format("AsyncDataSrc %d created DataConn", this.id)); + var conn = new AsyncDataConn(); + return conn; + } + } + + static class SyncDataConn implements DataConn { + @Override public void commit(AsyncGroup ag) throws Exc {} + @Override public void preCommit(AsyncGroup ag) throws Exc {} + @Override public void postCommit(AsyncGroup ag) {} + @Override public boolean shouldForceBack() { return false; } + @Override public void rollback(AsyncGroup ag) {} + @Override public void forceBack(AsyncGroup ag) {} + @Override public void close() {} + } + + static class AsyncDataConn implements DataConn { + @Override public void commit(AsyncGroup ag) throws Exc {} + @Override public void preCommit(AsyncGroup ag) throws Exc {} + @Override public void postCommit(AsyncGroup ag) {} + @Override public boolean shouldForceBack() { return false; } + @Override public void rollback(AsyncGroup ag) {} + @Override public void forceBack(AsyncGroup ag) {} + @Override public void close() {} + } + + @Test + void test_new() { + var dsList = new DataSrcList(false); + + assertThat(dsList.local).isEqualTo(false); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + } + + @Test + void test_appendContainerPtrNotSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrNotSetup(ptr1); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr1); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isNull(); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrNotSetup(ptr2); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr2); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isNull(); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeHeadContainerPtrNotSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrNotSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrNotSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrNotSetup(ptr1); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr2); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr2.prev).isNull(); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeMiddleContainerPtrNotSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrNotSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrNotSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrNotSetup(ptr2); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr1); + assertThat(ptr3.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeLastContainerPtrNotSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrNotSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrNotSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr2); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeAllContainerPtrNotSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrNotSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrNotSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrNotSetup(ptr1); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr2); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr2.prev).isNull(); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrNotSetup(ptr2); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr3); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr3.prev).isNull(); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrNotSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeAndCloseLocalContainerPtrNotSetupByName() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrNotSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrNotSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrNotSetup(ptr3); + + var ds4 = new SyncDataSrc(4, logger, false); + var ptr4 = new DataSrcContainer(false, "qux", ds4); + dsList.appendContainerPtrNotSetup(ptr4); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr4); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isEqualTo(ptr4); + assertThat(ptr4.prev).isEqualTo(ptr3); + assertThat(ptr4.next).isNull(); + + dsList.removeAndCloseContainerPtrNotSetupByName("bar"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr1); + assertThat(dsList.notSetupLast).isEqualTo(ptr4); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr1); + assertThat(ptr3.next).isEqualTo(ptr4); + assertThat(ptr4.prev).isEqualTo(ptr3); + assertThat(ptr4.next).isNull(); + + dsList.removeAndCloseContainerPtrNotSetupByName("foo"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr3); + assertThat(dsList.notSetupLast).isEqualTo(ptr4); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr3.prev).isNull(); + assertThat(ptr3.next).isEqualTo(ptr4); + assertThat(ptr4.prev).isEqualTo(ptr3); + assertThat(ptr4.next).isNull(); + + dsList.removeAndCloseContainerPtrNotSetupByName("qux"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isEqualTo(ptr3); + assertThat(dsList.notSetupLast).isEqualTo(ptr3); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + assertThat(ptr3.prev).isNull(); + assertThat(ptr3.next).isNull(); + + dsList.removeAndCloseContainerPtrNotSetupByName("baz"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + dsList.closeDataSrcs(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 closed", + "SyncDataSrc 1 closed", + "SyncDataSrc 4 closed", + "SyncDataSrc 3 closed" + ); + } + + @Test + void test_appendContainerPtrDidSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr1); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isNull(); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr2); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isNull(); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeHeadContainerPtrDidSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrDidSetup(ptr1); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr2); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr2.prev).isNull(); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeMiddleContainerPtrDidSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrDidSetup(ptr2); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr1); + assertThat(ptr3.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeLastContainerPtrDidSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr2); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeAllContainerPtrDidSetup() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrDidSetup(ptr1); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr2); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr2.prev).isNull(); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrDidSetup(ptr2); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr3); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr3.prev).isNull(); + assertThat(ptr3.next).isNull(); + + dsList.removeContainerPtrDidSetup(ptr3); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + dsList.closeDataSrcs(); + } + + @Test + void test_removeAndCloseLocalContainerPtrDidSetupByName() { + var dsList = new DataSrcList(false); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + var ds4 = new SyncDataSrc(4, logger, false); + var ptr4 = new DataSrcContainer(false, "qux", ds4); + dsList.appendContainerPtrDidSetup(ptr4); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr4); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr2); + assertThat(ptr2.prev).isEqualTo(ptr1); + assertThat(ptr2.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr2); + assertThat(ptr3.next).isEqualTo(ptr4); + assertThat(ptr4.prev).isEqualTo(ptr3); + assertThat(ptr4.next).isNull(); + + dsList.removeAndCloseContainerPtrDidSetupByName("bar"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr1); + assertThat(dsList.didSetupLast).isEqualTo(ptr4); + + assertThat(ptr1.prev).isNull(); + assertThat(ptr1.next).isEqualTo(ptr3); + assertThat(ptr3.prev).isEqualTo(ptr1); + assertThat(ptr3.next).isEqualTo(ptr4); + assertThat(ptr4.prev).isEqualTo(ptr3); + assertThat(ptr4.next).isNull(); + + dsList.removeAndCloseContainerPtrDidSetupByName("foo"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr3); + assertThat(dsList.didSetupLast).isEqualTo(ptr4); + + assertThat(ptr3.prev).isNull(); + assertThat(ptr3.next).isEqualTo(ptr4); + assertThat(ptr4.prev).isEqualTo(ptr3); + assertThat(ptr4.next).isNull(); + + dsList.removeAndCloseContainerPtrDidSetupByName("qux"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isEqualTo(ptr3); + assertThat(dsList.didSetupLast).isEqualTo(ptr3); + + assertThat(ptr3.prev).isNull(); + assertThat(ptr3.next).isNull(); + + dsList.removeAndCloseContainerPtrDidSetupByName("baz"); + + assertThat(dsList.local).isFalse(); + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.notSetupLast).isNull(); + assertThat(dsList.didSetupHead).isNull(); + assertThat(dsList.didSetupLast).isNull(); + + dsList.closeDataSrcs(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 closed", + "SyncDataSrc 1 closed", + "SyncDataSrc 4 closed", + "SyncDataSrc 3 closed" + ); + } + + @Test + void test_copyContainerPtrsDidSetupInto() { + var dsList = new DataSrcList(false); + + var m = new HashMap(); + dsList.copyContainerPtrsDidSetupInto(m); + assertThat(m).hasSize(0); + + var logger = new ArrayList(); + + var ds1 = new SyncDataSrc(1, logger, false); + var ptr1 = new DataSrcContainer(false, "foo", ds1); + dsList.appendContainerPtrDidSetup(ptr1); + + var ds2 = new SyncDataSrc(2, logger, false); + var ptr2 = new DataSrcContainer(false, "bar", ds2); + dsList.appendContainerPtrDidSetup(ptr2); + + var ds3 = new SyncDataSrc(3, logger, false); + var ptr3 = new DataSrcContainer(false, "baz", ds3); + dsList.appendContainerPtrDidSetup(ptr3); + + m = new HashMap(); + dsList.copyContainerPtrsDidSetupInto(m); + + assertThat(m).hasSize(3); + assertThat(m.get("foo")).isEqualTo(ptr1); + assertThat(m.get("bar")).isEqualTo(ptr2); + assertThat(m.get("baz")).isEqualTo(ptr3); + + dsList.closeDataSrcs(); + } + + @Test + void test_setupAndCreateDataConnAndClose() { + var logger = new ArrayList(); + + var dsList = new DataSrcList(false); + + var dsAsync = new AsyncDataSrc(1, logger, false); + dsList.addDataSrc("foo", dsAsync); + + var dsSync = new SyncDataSrc(2, logger, false); + dsList.addDataSrc("bar", dsSync); + + var errMap = dsList.setupDataSrcs(); + assertThat(errMap).isEmpty(); + + var ptr = dsList.didSetupHead; + try { + var conn = ptr.ds.createDataConn(); + assertThat(conn).isNotNull(); + } catch (Exc e) { + fail(e); + } + + ptr = ptr.next; + try { + var conn = ptr.ds.createDataConn(); + assertThat(conn).isNotNull(); + } catch (Exc e) { + fail(e); + } + + dsList.closeDataSrcs(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 setupped", + "AsyncDataSrc 1 created DataConn", + "SyncDataSrc 2 created DataConn", + "SyncDataSrc 2 closed", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void test_failToSetupSyncAndClose() { + var logger = new ArrayList(); + + var dsList = new DataSrcList(true); + + var dsAsync = new AsyncDataSrc(1, logger, false); + dsList.addDataSrc("foo", dsAsync); + + var dsSync = new SyncDataSrc(2, logger, true); + dsList.addDataSrc("bar", dsSync); + + var errMap = dsList.setupDataSrcs(); + assertThat(errMap).hasSize(1); + + var err = errMap.get("bar"); + assertThat(err.getReason()).isEqualTo("XXX"); + + dsList.closeDataSrcs(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 failed to setup", + "AsyncDataSrc 1 setupped", + "AsyncDataSrc 1 closed" + ); + } + + @Test + void test_failToSetupAsyncAndClose() { + var logger = new ArrayList(); + + var dsList = new DataSrcList(true); + + var dsAsync = new AsyncDataSrc(1, logger, true); + dsList.addDataSrc("foo", dsAsync); + + var dsSync = new SyncDataSrc(2, logger, false); + dsList.addDataSrc("bar", dsSync); + + var excMap = dsList.setupDataSrcs(); + assertThat(excMap).hasSize(1); + + var exc = excMap.get("foo"); + assertThat(exc.getReason()).isEqualTo("XXX"); + + dsList.closeDataSrcs(); + + assertThat(logger).containsExactly( + "SyncDataSrc 2 setupped", + "AsyncDataSrc 1 failed to setup", + "SyncDataSrc 2 closed" + ); + } + + @Test + void test_noDataSrc() { + var dsList = new DataSrcList(false); + + var excMap = dsList.setupDataSrcs(); + assertThat(excMap).hasSize(0); + + dsList.closeDataSrcs(); + + assertThat(dsList.notSetupHead).isNull(); + assertThat(dsList.didSetupHead).isNull(); + } +} From 6aefe35176faee5087513e51980b40388f79e968 Mon Sep 17 00:00:00 2001 From: sttk Date: Sun, 13 Jul 2025 14:25:45 +0900 Subject: [PATCH 2/2] fix: modified what pointed out --- src/test/java/com/github/sttk/sabi/internal/DataAccTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java b/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java index 9806dc8..4ec221b 100644 --- a/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java +++ b/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java @@ -63,7 +63,6 @@ static class FooDataConn implements DataConn { FooDataConn(int id, String text, List logger) { this.id = id; this.text = text; - this.committed = committed; this.logger = logger; } String getText() {