diff --git a/src/main/java/com/github/sttk/sabi/AsyncGroup.java b/src/main/java/com/github/sttk/sabi/AsyncGroup.java index 46f266e..51ce56f 100644 --- a/src/main/java/com/github/sttk/sabi/AsyncGroup.java +++ b/src/main/java/com/github/sttk/sabi/AsyncGroup.java @@ -4,38 +4,43 @@ */ package com.github.sttk.sabi; +import com.github.sttk.sabi.internal.AsyncGroupImpl; + /** - * An interface for asynchronously executing multiple {@link Runner} instances and waiting for their - * completion. + * Manages asynchronous background tasks executed during data source and data connection lifecycle + * events. + * + *

An instance of {@code AsyncGroup} is passed to methods of {@link DataSrc} (such as {@link + * DataSrc#setup(AsyncGroup)}) and {@link DataConn} (such as {@link DataConn#commit(AsyncGroup)} and + * {@link DataConn#rollback(AsyncGroup)}). Implementations of data sources and data connections can + * register background asynchronous operations (such as parallel cleanup or pre-commit validations) + * using the {@link #add(Runner)} method. * - *

Implementations of this interface allow adding multiple {@link Runner} objects, which are then - * executed concurrently. The group waits until all added runners have finished their execution. Any - * errors occurring during the execution of a {@link Runner} are stored and can be retrieved by - * their names in a map. + *

All registered {@link Runner} tasks are managed by this group and executed asynchronously. If + * any registered task fails, is interrupted, or throws an unhandled runtime exception, + * corresponding error records defined in this interface are produced to report the failure. */ -public sealed interface AsyncGroup permits com.github.sttk.sabi.internal.AsyncGroupImpl { +public sealed interface AsyncGroup permits AsyncGroupImpl { - /** - * Represents the reason for a new {@link com.github.sttk.errs.Err} exception object when an - * exception occurred during the execution of a {@link Runner} and the exception class was not the - * {@link com.github.sttk.errs.Err}. - */ + /** Indicates that a registered {@link Runner} task failed during its execution. */ record RunnerFailed() {} - /** - * Represents the reason for an {@link com.github.sttk.errs.Err} exception object when the - * creation of a thread for asynchronous execution of a {@link Runner} fails. - */ + /** Indicates that a registered {@link Runner} task execution was interrupted. */ record RunnerInterrupted() {} - /** Represents an unexpected {@link RuntimeException} that occurred. */ - record RuntimeExceptionOccurred() {} + /** + * Indicates that an unhandled runtime exception was thrown during the execution of a registered + * {@link Runner} task. + */ + record RuntimeExceptionOccured() {} /** - * Adds a {@link Runner} to this group for asynchronous execution. The added runner will be - * executed in a separate thread. + * Adds a background task to be executed asynchronously by this group. + * + *

The task is encapsulated in a {@link Runner} functional interface and scheduled for parallel + * asynchronous execution. * - * @param runner The {@link Runner} to be added and executed asynchronously. + * @param runner the {@link Runner} task to be added and executed asynchronously */ - void add(final Runner runner); + void add(Runner runner); } diff --git a/src/main/java/com/github/sttk/sabi/DataAcc.java b/src/main/java/com/github/sttk/sabi/DataAcc.java index 2a6855c..1e58c89 100644 --- a/src/main/java/com/github/sttk/sabi/DataAcc.java +++ b/src/main/java/com/github/sttk/sabi/DataAcc.java @@ -7,25 +7,29 @@ import com.github.sttk.errs.Err; /** - * An interface designed for implementing data access operations through default methods in its - * sub-interfaces. + * Provides access to named {@link DataConn} data connection instances. * - *

Sub-interfaces of {@code DataAcc} are expected to define and implement data access methods as - * default methods. Within these default methods, the connection to the underlying data store should - * be obtained using the {@link #getDataConn(String, Class)} method provided by this interface. This - * design promotes a clear separation of concerns, allowing data access logic to be encapsulated - * within the interface itself. + *

This interface defines the contract for retrieving managed data connections registered under + * specific names within a data access scope (such as a {@link DataHub}). Application business logic + * uses this interface to obtain connection objects required to perform database or external service + * operations. */ public interface DataAcc { + /** - * Retrieves a connection to a data store. This method is intended to be used by default methods - * in sub-interfaces to obtain the necessary connection for performing data access operations. + * Retrieves a data connection associated with the specified data source name and casts it to the + * requested connection class type. + * + *

If a connection for the given {@code name} has already been created within the current + * transaction or execution scope, that connection instance is returned. Otherwise, a new + * connection is created via the corresponding registered data source. * - * @param The type of the data connection, which must extend {@link DataConn}. - * @param name The name identifying the specific data connection to retrieve. - * @param cls The {@link Class} object representing the type of the desired data connection. - * @return A data connection object of the specified type. - * @throws Err if an error occurs while obtaining the data connection. + * @param the expected type of {@link DataConn} + * @param name the registered logical name of the data source + * @param cls the {@link Class} representing the target connection type {@code C} + * @return the data connection instance associated with the specified name + * @throws Err if no data source with the specified name is found, if creating the connection + * fails or yields null, or if the connection cannot be cast to the target type */ C getDataConn(String name, Class cls) throws Err; } diff --git a/src/main/java/com/github/sttk/sabi/DataConn.java b/src/main/java/com/github/sttk/sabi/DataConn.java index 6240219..e327093 100644 --- a/src/main/java/com/github/sttk/sabi/DataConn.java +++ b/src/main/java/com/github/sttk/sabi/DataConn.java @@ -5,78 +5,105 @@ package com.github.sttk.sabi; import com.github.sttk.errs.Err; +import java.util.List; /** - * The interface that abstracts a connection per session to an external data service, such as a - * database, file system, or messaging service. + * Represents a data connection participating in transaction lifecycles managed by a {@link + * DataHub}. * - *

Its primary purpose is to enable cohesive transaction operations across multiple external data - * services within a single transaction context. Implementations of this interface provide the - * concrete input/output operations for their respective data services. - * - *

Methods declared within this interface are designed to handle transactional logic. The - * AsyncGroup parameter in various methods allows for asynchronous processing when commit or - * rollback operations are time-consuming. + *

Implementations encapsulate connection state and operations for external resources such as + * relational databases, NoSQL stores, or web services. During a transaction, connections progress + * through pre-commit, commit, and post-commit phases, and support rollback and failure reporting + * when errors occur. */ public interface DataConn { + /** - * Commits the changes made within the current session to the external data service. This method - * is responsible for finalizing all operations performed since the last commit or rollback. + * Represents an error when one or more data connections fail during the pre-commit phase. * - * @param ag An {@link AsyncGroup} that can be used to perform asynchronous operations if the - * commit process is time-consuming. - * @throws Err if an error occurs during the commit operation. + * @param errors the list of error entries detailing connection names and failure causes */ - void commit(AsyncGroup ag) throws Err; + public record FailToPreCommitDataConn(List errors) {} /** - * Performs any necessary pre-commit operations. This method is called before the {@link - * #commit(AsyncGroup)} method. + * Represents an error when one or more data connections fail during the commit phase. * - * @param ag An {@link AsyncGroup} that can be used for asynchronous pre-commit tasks. - * @throws Err if an error occurs during the pre-commit operation. + * @param errors the list of error entries detailing connection names and failure causes */ - default void preCommit(AsyncGroup ag) throws Err {} + public record FailToCommitDataConn(List errors) {} + + /** + * Represents an error when one or more data connections fail during the post-commit phase. + * + * @param errors the list of error entries detailing connection names and failure causes + */ + public record FailToPostCommitDataConn(List errors) {} + + /// + + /** + * Commits changes made through this connection. + * + *

Async background tasks may be registered to the provided {@link AsyncGroup} during commit + * processing. + * + * @param ag the asynchronous group for registering background tasks + * @throws Err if committing the transaction changes fails + */ + void commit(AsyncGroup ag) throws Err; /** - * Performs any necessary post-commit operations. This method is called after the {@link - * #commit(AsyncGroup)} method has successfully completed. + * Prepares this connection for commit prior to the main commit phase. + * + *

The default implementation does nothing. Subclasses can override this method to perform + * pre-commit checks or flush pending writes. * - * @param ag An {@link AsyncGroup} that can be used for asynchronous post-commit tasks. + * @param ag the asynchronous group for registering background tasks + * @throws Err if pre-commit preparation or validation fails */ - default void postCommit(AsyncGroup ag) {} + default void preCommit(AsyncGroup ag) throws Err {} /** - * Indicates whether a force-back operation should be performed. A force-back is a mechanism to - * revert the committed changes when this connection had been already committed but the other - * connection had failed. + * Performs post-commit operations after all connections in a transaction have successfully + * committed. * - * @return {@code true} if a force-back is required, {@code false} otherwise. + *

The default implementation does nothing. Subclasses can override this method to perform + * cleanup or trigger post-commit notifications. + * + * @param ag the asynchronous group for registering background tasks + * @throws Err if post-commit processing fails */ - default boolean shouldForceBack() { - return false; - } + default void postCommit(AsyncGroup ag) throws Err {} /** - * Rolls back the changes made within the current session, discarding all operations performed - * since the last commit or rollback. + * Checks whether this connection has been successfully committed. * - * @param ag An {@link AsyncGroup} that can be used to perform asynchronous operations if the - * rollback process is time-consuming. + * @return {@code true} if this connection was committed; {@code false} otherwise */ - void rollback(AsyncGroup ag); + boolean isCommitted(); /** - * Performs a force-back operation to revert the committed changes when this connection had been - * already committed but the other connection had failed. + * Rolls back changes made through this connection. + * + *

This method is invoked when a transaction fails and must restore the connection or + * underlying storage to its pre-transaction state. * - * @param ag An {@link AsyncGroup} that can be used for asynchronous force-back tasks. + * @param ag the asynchronous group for registering background tasks + * @throws Err if rolling back connection changes fails */ - default void forceBack(AsyncGroup ag) {} + void rollback(AsyncGroup ag) throws Err; /** - * Closes the connection to the external data service, releasing any associated resources. This - * method should be called to ensure proper resource management. + * Notifies this connection of a transaction failure with detailed failure reports. + * + *

The default implementation does nothing. Implementations can inspect failure reports to log + * diagnostics or take recovery actions. + * + * @param ag the asynchronous group for registering background tasks + * @param reports the list of transaction failure reports */ + default void onTxnFailure(AsyncGroup ag, List reports) {} + + /** Closes and disposes of this data connection, releasing any held resources. */ void close(); } diff --git a/src/main/java/com/github/sttk/sabi/DataHub.java b/src/main/java/com/github/sttk/sabi/DataHub.java index e3f6908..9b22674 100644 --- a/src/main/java/com/github/sttk/sabi/DataHub.java +++ b/src/main/java/com/github/sttk/sabi/DataHub.java @@ -6,239 +6,212 @@ import com.github.sttk.errs.Err; import com.github.sttk.sabi.internal.DataHubInner; -import java.util.Map; +import com.github.sttk.sabi.internal.TxnFailureReportBuilder; +import java.util.ArrayList; +import java.util.List; /** - * {@code DataHub} is a central component in the Sabi framework that manages {@link DataSrc} and - * {@link DataConn} instances, facilitating data access and transaction management. It implements - * both {@link DataAcc} for data access operations and {@link AutoCloseable} for resource - * management. + * Manages local data sources and data connections, and executes business logic in transactional or + * non-transactional scopes. * - *

This class allows for the registration and unregistration of local {@link DataSrc} objects, - * and provides methods to execute application logic with or without transactional boundaries. + *

A {@code DataHub} acts as a container for local {@link DataSrc} instances and provides access + * to connections implementing {@link DataConn}. It supports executing logic functions through + * {@link #run(Logic)} for non-transactional operations and {@link #txn(Logic)} for transactional + * operations. In a transaction, connection commits, rollbacks, and failure reporting are handled + * automatically. */ public class DataHub implements DataAcc, AutoCloseable { - /** - * Represents an error reason that occurred when failing to set up global {@link DataSrc} - * instances. - * - * @param errors A map containing the names of the data sources and the corresponding exceptions - * that occurred during their setup. - */ - public record FailToSetupGlobalDataSrcs(Map errors) {} - - /** - * Represents an error reason that occurred when failing to set up local {@link DataSrc} - * instances. - * - * @param errors A map containing the names of the data sources and the corresponding exceptions - * that occurred during their setup. - */ - public record FailToSetupLocalDataSrcs(Map errors) {} /** - * Represents an error reason that occurred when failing to commit one or more {@link DataConn} - * instances. + * Represents an error when setting up global data sources fails. * - * @param errors A map containing the names of the data connections and the corresponding - * exceptions that occurred during their commit. + * @param errors the list of error entries during global data source setup */ - public record FailToCommitDataConn(Map errors) {} + public record FailToSetupGlobalDataSrcs(List errors) {} /** - * Represents an error reason that occurred when failing to pre-commit one or more {@link - * DataConn} instances. + * Represents an error when setting up local data sources fails. * - * @param errors A map containing the names of the data connections and the corresponding - * exceptions that occurred during their pre-commit. + * @param errors the list of error entries during local data source setup */ - public record FailToPreCommitDataConn(Map errors) {} + public record FailToSetupLocalDataSrcs(List errors) {} /** - * Represents an error reason where no {@link DataSrc} was found to create a {@link DataConn} with - * the specified name and type. + * Represents an error when no data source with the specified name is found. * - * @param name The name of the data source requested. - * @param dataConnType The type of the data connection requested. + * @param name the logical name of the data source + * @param dataConnType the expected data connection class name */ public record NoDataSrcToCreateDataConn(String name, String dataConnType) {} /** - * Represents an error reason that occurred when failing to create a {@link DataConn} instance. + * Represents an error when creating a data connection fails. * - * @param name The name of the data source from which the connection was attempted. - * @param dataConnType The type of the data connection that failed to be created. + * @param name the logical name of the data source + * @param dataConnType the expected data connection class name */ public record FailToCreateDataConn(String name, String dataConnType) {} /** - * Represents an error reason where the created {@link DataConn} instance was null. + * Represents an error when a created data connection instance is null. * - * @param name The name of the data source. - * @param dataConnType The type of the data connection expected. + * @param name the logical name of the data source + * @param dataConnType the expected data connection class name */ public record CreatedDataConnIsNull(String name, String dataConnType) {} /** - * Represents an error reason that occurred when failing to cast a {@link DataConn} to the - * requested type. + * Represents an error when a connection cannot be cast to the target connection type. * - * @param name The name of the data connection. - * @param castToType The type to which the data connection was attempted to be cast. + * @param name the logical name of the data source + * @param fromDataConnType the actual connection class name + * @param toDataConnType the target connection class name */ - public record FailToCastDataConn(String name, String castToType) {} + public record FailToCastDataConn(String name, String fromDataConnType, String toDataConnType) {} /** - * Represents an unexpected {@link RuntimeException} that occurred during pre-commit or commit - * operations. + * Represents an error when casting this {@code DataHub} instance to the generic data context + * fails. + * + * @param fromType the class name of this DataHub instance */ + public record FailToCastDataHub(String fromType) {} + + /** Represents an error when an unhandled runtime exception occurs during logic execution. */ public record RuntimeExceptionOccurred() {} - /** - * Represents an error reason that occurred when failing to cast the {@code DataHub} instance - * itself to the expected data access interface type for a {@link Logic}. - * - * @param castFromType The actual type of the {@code DataHub} instance that failed to cast. - */ - public record FailToCastDataHub(String castFromType) {} + /// - private final DataHubInner inner = new DataHubInner(); + private final DataHubInner inner; - /** Constructs a new {@code DataHub} instance. */ - public DataHub() {} + /** Constructs a new, default {@code DataHub} instance. */ + public DataHub() { + this.inner = new DataHubInner(); + } /** - * Registers a local {@link DataSrc} with the specified name for use within this {@code DataHub} - * instance. This allows specific data sources to be managed independently from globally - * registered ones. + * Constructs a new {@code DataHub} instance configured to use specified global data sources. * - * @param name The unique name for the {@link DataSrc}. - * @param ds The {@link DataSrc} instance to register. + * @param names the list of global data source names to bind to this hub */ - public void uses(String name, DataSrc ds) { - inner.uses(name, ds); + public DataHub(List names) { + this.inner = new DataHubInner(names); } /** - * Unregisters a local {@link DataSrc} with the given name from this {@code DataHub} instance. + * Constructs a new {@code DataHub} instance configured to use specified global data sources. * - * @param name The name of the {@link DataSrc} to unregister. + * @param names varargs array of global data source names to bind to this hub */ - public void disuses(String name) { - inner.disuses(name); + public DataHub(String... names) { + this(List.of(names)); } /** - * Retrieves a {@link DataConn} instance from the managed data sources. This method is part of the - * {@link DataAcc} interface implementation. - * - * @param The type of the {@link DataConn} to retrieve, which must extend {@link DataConn}. - * @param name The name of the data source from which to get the connection. - * @param cls The {@link Class} object representing the desired type of the data connection. - * @return A {@link DataConn} instance of the specified type. - * @throws Err if no data source is found, if the connection cannot be created, if the created - * connection is null, or if the connection cannot be cast to the specified class. + * Registers a local {@link DataSrc} with a specific name in this hub. + * + * @param name the logical name for the data source + * @param ds the {@link DataSrc} instance to register */ - @Override - public C getDataConn(String name, Class cls) throws Err { - return inner.getDataConn(name, cls); + public void uses(String name, DataSrc ds) { + this.inner.useLocal(name, ds); } /** - * Closes all {@link DataConn} instances managed by this {@code DataHub}, releasing their - * resources. This method is part of the {@link AutoCloseable} interface and should be called to - * ensure proper resource cleanup, ideally in a try-with-resources statement. + * Unregisters a local {@link DataSrc} associated with the given name from this hub. + * + * @param name the logical name of the data source to remove */ - @Override - public void close() { - inner.close(); - } - - void begin() throws Err { - inner.begin(); - } - - void commit() throws Err { - inner.commit(); + public void disuses(String name) { + this.inner.disuseLocal(name); } - void rollback() { - inner.rollback(); + /** Closes all local data sources registered in this hub and releases their resources. */ + @Override + public void close() { + this.inner.closeLocals(); } - void end() { - inner.end(); + /** + * {@inheritDoc} + * + * @throws Err if the data source is not found, connection creation fails or returns null, or + * casting to {@code cls} fails + */ + @Override + public C getDataConn(String name, Class cls) throws Err { + return this.inner.getDataConn(name, cls); } /** - * Executes the provided application {@link Logic} without transactional boundaries. The {@code - * DataHub} instance is treated as the data access object {@code D} to the {@link Logic}'s {@code - * run} method. - * - * @param The type of the data access object, which typically is {@code DataHub} or an - * interface implemented by {@code DataHub} that {@link Logic} expects. - * @param logic The application logic to execute. - * @throws Err if an {@link Err} or {@link RuntimeException} occurs during logic execution or if - * the {@code DataHub} cannot be cast to the expected data access type. + * Executes business logic in a non-transactional scope. + * + *

This method passes this hub instance (cast to type {@code D}) to {@link Logic#run(Object)}. + * Connections acquired during execution are managed within the scope. + * + * @param the type of data context expected by the logic, typically {@code DataHub} or a + * subclass/interface + * @param logic the business logic to execute + * @throws Err if the logic throws {@code Err}, if casting to {@code D} fails (wrapping {@link + * FailToCastDataHub}), or if an unhandled runtime exception occurs (wrapping {@link + * RuntimeExceptionOccurred}) */ public void run(Logic logic) throws Err { - D data; try { @SuppressWarnings("unchecked") - D d = (D) this; - data = d; - } catch (Exception e) { - throw new Err(new FailToCastDataHub(this.getClass().getName())); - } - try { - this.begin(); + D data = (D) this; + + this.inner.begin(); logic.run(data); - } catch (Err e) { - throw e; - } catch (RuntimeException e) { - throw new Err(new RuntimeExceptionOccurred(), e); + } catch (Err err) { + throw err; + } catch (ClassCastException e) { + throw new Err(new FailToCastDataHub(this.getClass().getName()), e); + } catch (RuntimeException re) { + throw new Err(new RuntimeExceptionOccurred(), re); } finally { - this.end(); + this.inner.end(); } } /** - * Executes the provided application {@link Logic} within a transactional context. The {@code - * DataHub} instance is treated as the data access object {@code D} to the {@link Logic}'s {@code - * run} method. If the logic completes successfully, a commit operation is attempted. If any - * {@link Err}, {@link RuntimeException}, or {@link Error} occurs, a rollback operation is - * performed. - * - * @param The type of the data access object, which typically is {@code DataHub} or an - * interface implemented by {@code DataHub} that {@link Logic} expects. - * @param logic The application logic to execute transactionally. - * @throws Err if an {@link Err}, {@link RuntimeException}, or {@link Error} occurs during logic - * execution, pre-commit, or commit. The original exception is re-thrown after rollback. + * Executes business logic within a transactional boundary. + * + *

Upon successful completion of {@link Logic#run(Object)}, all acquired data connections are + * committed. If an exception occurs, all connections are automatically rolled back, transaction + * failure reports are dispatched to connections, and the exception is rethrown wrapped in {@link + * Err}. + * + * @param the type of data context expected by the logic + * @param logic the business logic to execute transactionally + * @throws Err if the logic throws {@code Err}, connection commit or rollback fails, casting to + * {@code D} fails, or a runtime exception occurs */ public void txn(Logic logic) throws Err { - D data; + final var reportBuilders = new ArrayList(0); try { - @SuppressWarnings("unchecked") - D d = (D) this; - data = d; - } catch (Exception e) { - throw new Err(new FailToCastDataHub(this.getClass().getName())); - } - try { - this.begin(); - logic.run(data); - this.commit(); - } catch (Err e) { - this.rollback(); - throw e; - } catch (RuntimeException e) { - this.rollback(); - throw new Err(new RuntimeExceptionOccurred(), e); - } catch (Error e) { - this.rollback(); - throw e; + try { + @SuppressWarnings("unchecked") + D data = (D) this; + + this.inner.begin(); + logic.run(data); + } finally { + this.inner.prepareTxnFailureReportBuilders(reportBuilders); + } + + this.inner.commit(reportBuilders); + } catch (Err err) { + this.inner.rollback(reportBuilders); + throw err; + } catch (ClassCastException e) { + this.inner.rollback(reportBuilders); + throw new Err(new FailToCastDataHub(this.getClass().getName()), e); + } catch (RuntimeException re) { + this.inner.rollback(reportBuilders); + throw new Err(new RuntimeExceptionOccurred(), re); } finally { - this.end(); + this.inner.end(); } } } diff --git a/src/main/java/com/github/sttk/sabi/DataSrc.java b/src/main/java/com/github/sttk/sabi/DataSrc.java index 0067ba8..609bde4 100644 --- a/src/main/java/com/github/sttk/sabi/DataSrc.java +++ b/src/main/java/com/github/sttk/sabi/DataSrc.java @@ -7,38 +7,36 @@ import com.github.sttk.errs.Err; /** - * The interface that abstracts a data source responsible for managing connections to external data - * services, such as databases, file systems, or messaging services. + * Represents a data source corresponding to an external data resource, serving as a factory for + * data connections or as a connection pool. * - *

It receives configuration for connecting to an external data service and then creates and - * supplies a {@link DataConn} instance, representing a single session connection. + *

Implementations encapsulate connection management, pool configuration, and resource allocation + * for specific data stores or external services (such as relational databases, HTTP clients, or + * caching services). Data sources can be registered globally via {@link Sabi#uses(String, DataSrc)} + * or bound locally to a {@link DataHub}. */ public interface DataSrc { + /** - * Sets up the data source, performing any necessary initialization or configuration to establish - * connectivity to the external data service. This method is typically called once at the - * application startup. + * Sets up this data source. + * + *

This method is invoked during application startup or hub setup to prepare connections or + * connection pools. Asynchronous setup background tasks can be registered using the provided + * {@link AsyncGroup}. * - * @param ag An {@link AsyncGroup} that can be used for asynchronous setup operations, especially - * if initialization is time-consuming. - * @throws Err if an error occurs during the setup process. + * @param ag the asynchronous group for registering background tasks + * @throws Err if setting up the data source fails */ void setup(AsyncGroup ag) throws Err; - /** - * Closes the data source, releasing all resources and shutting down connections managed by this - * source. This method should be called when the application is shutting down to ensure proper - * resource cleanup. - */ + /** Closes this data source and disposes of any allocated resources or connection pools. */ void close(); /** - * Creates and returns a new {@link DataConn} instance, representing a single session connection - * to the external data service. Each call to this method should yield a new, independent - * connection. + * Creates a new {@link DataConn} instance associated with this data source. * - * @return A new {@link DataConn} instance for a session. - * @throws Err if an error occurs while creating the data connection. + * @return a new, initialized {@link DataConn} connection instance + * @throws Err if creating or establishing the data connection fails */ DataConn createDataConn() throws Err; } diff --git a/src/main/java/com/github/sttk/sabi/ErrEntry.java b/src/main/java/com/github/sttk/sabi/ErrEntry.java new file mode 100644 index 0000000..3dd4d18 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/ErrEntry.java @@ -0,0 +1,40 @@ +/* + * ErrEntry.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.errs.Err; + +/** + * Represents an error entry containing details of a failure associated with a specific data source + * or data connection. + * + *

Instances of this class record the positional index, logical name, and cause of an error when + * batch operations (such as global/local data source setup or connection pre-commit, commit, and + * post-commit phases) encounter errors across multiple connections. + */ +public class ErrEntry { + + /** The positional index of the data source or connection in the operation sequence. */ + public final int index; + + /** The logical name of the data source or connection where the error occurred. */ + public final String name; + + /** The error instance containing details of the failure cause. */ + public final Err err; + + /** + * Constructs a new {@code ErrEntry} with the specified index, name, and error cause. + * + * @param index the positional index of the data source or connection + * @param name the logical name of the data source or connection + * @param err the error instance detailing the failure cause + */ + public ErrEntry(int index, String name, Err err) { + this.index = index; + this.name = name; + this.err = err; + } +} diff --git a/src/main/java/com/github/sttk/sabi/Logic.java b/src/main/java/com/github/sttk/sabi/Logic.java index 0ff1fce..0fa564b 100644 --- a/src/main/java/com/github/sttk/sabi/Logic.java +++ b/src/main/java/com/github/sttk/sabi/Logic.java @@ -7,27 +7,24 @@ import com.github.sttk.errs.Err; /** - * Represents the application's business logic, designed to separate data access concerns from the - * core logic. + * Functional interface representing a unit of business logic executed within a {@link DataHub} + * scope. * - *

Implementations of this functional interface should focus solely on the business logic, - * utilizing the provided {@code data} object of type {@code D} for all data access operations. The - * {@link #run(Object)} method should not contain any direct data access code; instead, it should - * delegate such operations to methods of the {@code D} object. + *

Instances of this interface are passed to {@link DataHub#run(Logic)} for non-transactional + * execution or {@link DataHub#txn(Logic)} for transactional execution. The provided data access + * interface {@code data} gives access to data connections and resources needed during execution. * - *

If an exceptional condition occurs during the execution of the logic, an {@link Err} object - * should be thrown. - * - * @param The type of the data access object through which data operations are performed. + * @param the type of data access interface passed to the logic execution method */ @FunctionalInterface public interface Logic { + /** - * Executes the application's business logic. This method should implement the core logic, relying - * on the {@code data} object for all data access needs. + * Executes the business logic using the provided data access interface. * - * @param data The data access object, providing methods for interacting with data. - * @throws Err if an error or exceptional condition occurs during the logic execution. + * @param data the data access interface instance (typically a {@link DataHub} or custom data + * access interface) + * @throws Err if an error occurs during logic execution */ void run(D data) throws Err; } diff --git a/src/main/java/com/github/sttk/sabi/Runner.java b/src/main/java/com/github/sttk/sabi/Runner.java index a75277e..eae7833 100644 --- a/src/main/java/com/github/sttk/sabi/Runner.java +++ b/src/main/java/com/github/sttk/sabi/Runner.java @@ -6,15 +6,20 @@ import com.github.sttk.errs.Err; -/** {@code Runner} is the interface that runs any procedure. */ +/** + * Functional interface representing an asynchronous task to be executed by an {@link AsyncGroup}. + * + *

Tasks implementing this interface are added to an {@link AsyncGroup} (via {@link + * AsyncGroup#add(Runner)}) during data source or data connection lifecycle events (such as setup, + * commit, or rollback) to perform parallel background processing. + */ @FunctionalInterface 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. + * Runs the asynchronous background task. * - * @throws Err If this method failed. + * @throws Err if an error occurs during task execution */ void run() throws Err; } diff --git a/src/main/java/com/github/sttk/sabi/Sabi.java b/src/main/java/com/github/sttk/sabi/Sabi.java index 17bd782..8e821dc 100644 --- a/src/main/java/com/github/sttk/sabi/Sabi.java +++ b/src/main/java/com/github/sttk/sabi/Sabi.java @@ -6,61 +6,58 @@ import com.github.sttk.errs.Err; import com.github.sttk.sabi.internal.DataHubInner; +import java.util.List; /** - * {@code Sabi} is the class that provides the static methods related to the global functionalities - * of sabi framework. + * Utility class for managing and setting up global {@link DataSrc} instances. * - *

This class declares {@link #uses uses} method to register a {@link DataSrc} object used - * globally with its name. And this class also declares {@link #setup setup} methods, which is the - * static method to setup all global registered {@link DataSrc} objects. - * - *

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

   public class Application {
- *       static {
- *           Sabi.uses("foo", new FooDataSrc());
- *           Sabi.uses("bar", new BarDataSrc());
- *       }
- *       public static void main(String ...args) {
- *           int exitCode = 0;
- *           try (var ac = Sabi.setup()) {
- *               ...
- *           } catch (Exception e) {
- *               exitCode = 1;
- *           }
- *           System.exit(exitCode);
- *       }
- *   }
+ *

Applications can register global data sources using {@link #uses(String, DataSrc)} during + * startup, and then call {@link #setup()} or {@link #setup(String...)} to set them up. The returned + * {@link AutoCloseable} handles shutting down and closing global data sources when the application + * terminates or finishes using them. */ public final class Sabi { private Sabi() {} /** - * Registers a {@link DataSrc} object with a unique name for global use within the Sabi framework. - * This method should typically be called in a static initializer block of your application's main - * class. + * Registers a global data source with the specified logical name. * - * @param name The unique name to associate with the {@link DataSrc}. - * @param ds The {@link DataSrc} instance to be registered. + * @param name the logical name for the global data source + * @param ds the {@link DataSrc} instance to register */ public static void uses(String name, DataSrc ds) { - DataHubInner.usesGlobal(name, ds); + DataHubInner.useGlobal(name, ds); } /** - * Sets up all globally registered {@link DataSrc} objects. This involves calling the {@link - * DataSrc#setup(AsyncGroup) setup} method on each registered data source. This method should be - * called once at the application startup. + * Sets up all registered global data sources in their registration order. * - *

The returned {@link AutoCloseable} object can be used in a try-with-resources statement to - * automatically invoke the close operations upon exiting the try block. - * - * @return An {@link AutoCloseable} object that, when closed, will trigger the global close - * operation. - * @throws Err if an error occurs during the setup of any {@link DataSrc}. + * @return an {@link AutoCloseable} that closes all initialized global data sources when closed + * @throws Err if setting up any global data source fails */ public static AutoCloseable setup() throws Err { return DataHubInner.setupGlobals(); } + + /** + * Sets up registered global data sources matching the specified names in the given order. + * + * @param names varargs array of global data source names to set up + * @return an {@link AutoCloseable} that closes the set-up global data sources when closed + * @throws Err if setting up any of the specified global data sources fails + */ + public static AutoCloseable setup(String... names) throws Err { + return DataHubInner.setupGlobalsWithOrder(List.of(names)); + } + + /** + * Sets up registered global data sources matching the specified list of names in the given order. + * + * @param names list of global data source names to set up + * @return an {@link AutoCloseable} that closes the set-up global data sources when closed + * @throws Err if setting up any of the specified global data sources fails + */ + public static AutoCloseable setup(List names) throws Err { + return DataHubInner.setupGlobalsWithOrder(names); + } } diff --git a/src/main/java/com/github/sttk/sabi/TxnFailureCause.java b/src/main/java/com/github/sttk/sabi/TxnFailureCause.java new file mode 100644 index 0000000..b17c785 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/TxnFailureCause.java @@ -0,0 +1,48 @@ +/* + * TxnFailureCause.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.errs.Err; + +/** + * Represents the cause of a transaction failure, containing state information and the associated + * error. + * + *

This class encapsulates the phase or status in which a transaction failure occurred (via + * {@link TxnFailureCauseState}) and the specific {@link Err} instance detailing the failure. + */ +public class TxnFailureCause { + + /** The transaction failure cause state indicating the phase or status of the failure. */ + public final TxnFailureCauseState state; + + /** The error instance associated with the transaction failure, or {@code null} if none. */ + public final Err err; + + /** + * Constructs a new {@code TxnFailureCause} with the specified failure state and error. + * + * @param state the transaction failure cause state + * @param err the error associated with the failure + */ + public TxnFailureCause(TxnFailureCauseState state, Err err) { + this.state = state; + this.err = err; + } + + /** + * Returns a string representation of this transaction failure cause. + * + * @return a formatted string containing the state and error details + */ + public String toString() { + var buf = new StringBuilder("{"); + buf.append("State:").append(this.state); + buf.append(" "); + buf.append("Err:").append(this.err); + buf.append("}"); + return buf.toString(); + } +} diff --git a/src/main/java/com/github/sttk/sabi/TxnFailureCauseState.java b/src/main/java/com/github/sttk/sabi/TxnFailureCauseState.java new file mode 100644 index 0000000..42211cc --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/TxnFailureCauseState.java @@ -0,0 +1,24 @@ +/* + * TxnFailureCause.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +/** Defines the states representing causes of transaction failures or successful states. */ +public enum TxnFailureCauseState { + + /** Indicates no failure occurred and the connection was committed successfully. */ + NoneByCommitted, + + /** Indicates no failure occurred and the connection remained uncommitted. */ + NoneByUncommitted, + + /** Indicates that the transaction failed due to an error during logic execution. */ + LogicFailure, + + /** Indicates that the transaction failed during the commit phase of a connection. */ + CommitFailure, + + /** Indicates that the transaction failed during the post-commit phase of a connection. */ + PostCommitFailure, +} diff --git a/src/main/java/com/github/sttk/sabi/TxnFailureRecovery.java b/src/main/java/com/github/sttk/sabi/TxnFailureRecovery.java new file mode 100644 index 0000000..b8fe3db --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/TxnFailureRecovery.java @@ -0,0 +1,30 @@ +/* + * TxnFailureRecovery.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +/** Defines recommended recovery actions to handle transaction failures. */ +public enum TxnFailureRecovery { + + /** Indicates that no recovery action is required. */ + NoActionRequired, + + /** Indicates that the logic should be rerun and committed again without further changes. */ + RerunLogicAndCommit, + + /** Indicates that the root cause must be resolved before rerunning logic and committing. */ + ResolveCauseThenRerunLogicAndCommit, + + /** Indicates that the root cause must be resolved before rerunning the post-commit phase. */ + ResolveCauseThenRerunPostCommit, + + /** Indicates that both the root cause and any resulting data inconsistency must be resolved. */ + ResolveCauseAndInconsistency, + + /** Indicates an impossible state occurred that requires investigation. */ + InvestigateBecauseImpossible, + + /** Indicates that manual intervention is required to roll back changes. */ + ManualRollbackRequired, +} diff --git a/src/main/java/com/github/sttk/sabi/TxnFailureReport.java b/src/main/java/com/github/sttk/sabi/TxnFailureReport.java new file mode 100644 index 0000000..4c4101a --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/TxnFailureReport.java @@ -0,0 +1,200 @@ +/* + * TxnFailureReport.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +/** + * Contains transaction failure diagnostic information and rollback status for a specific data + * connection. + * + *

Instances of this class are created during transaction failure processing in {@link DataHub} + * and passed to {@link DataConn#onTxnFailure(AsyncGroup, java.util.List)}. It details the + * connection's name, type, failure cause ({@link TxnFailureCause}), and rollback status ({@link + * TxnFailureRollback}), and provides methods to determine recommended recovery strategies. + */ +public class TxnFailureReport { + + /** The logical name of the data connection. */ + public final String dataConnName; + + /** The class name or type of the data connection. */ + public final String dataConnType; + + /** The cause of the transaction failure associated with this connection. */ + public final TxnFailureCause cause; + + /** The rollback outcome associated with this connection. */ + public final TxnFailureRollback rollback; + + /** + * Constructs a new {@code TxnFailureReport} with the specified connection details, cause, and + * rollback outcome. + * + * @param name the logical name of the data connection + * @param type the class name or type of the data connection + * @param cause the transaction failure cause + * @param rollback the rollback outcome + */ + public TxnFailureReport( + String name, String type, TxnFailureCause cause, TxnFailureRollback rollback) { + this.dataConnName = name; + this.dataConnType = type; + this.cause = cause; + this.rollback = rollback; + } + + /** + * Returns a string representation of this transaction failure report. + * + * @return a formatted string containing connection details, cause, and rollback outcome + */ + public String toString() { + var buf = new StringBuilder("{"); + buf.append("dataConnName:").append(this.dataConnName); + buf.append(" "); + buf.append("dataConnType:").append(this.dataConnType); + buf.append(" "); + buf.append("cause:").append(this.cause); + buf.append(" "); + buf.append("rollback:").append(this.rollback); + buf.append("}"); + return buf.toString(); + } + + /** + * Determines whether this connection was a direct cause of the transaction failure. + * + * @return {@code true} if this connection caused the failure; {@code false} if the cause state is + * {@code NoneByCommitted} or {@code NoneByUncommitted} + */ + public boolean isCauseOfFailure() { + switch (this.cause.state) { + case NoneByCommitted: + case NoneByUncommitted: + return false; + default: + return true; + } + } + + /** + * Determines the recommended recovery strategy if the intent is to re-attempt and complete the + * transaction commit. + * + * @return the recommended {@link TxnFailureRecovery} action for committing + */ + public TxnFailureRecovery recoveryForCommit() { + switch (this.cause.state) { + case NoneByUncommitted: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.InvestigateBecauseImpossible; + case NoneByRolledBack: + return TxnFailureRecovery.RerunLogicAndCommit; + case RollbackFailure: + return TxnFailureRecovery.ResolveCauseAndInconsistency; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case NoneByCommitted: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.NoActionRequired; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case LogicFailure: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.InvestigateBecauseImpossible; + case NoneByRolledBack: + return TxnFailureRecovery.ResolveCauseThenRerunLogicAndCommit; + case RollbackFailure: + return TxnFailureRecovery.ResolveCauseAndInconsistency; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case CommitFailure: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.InvestigateBecauseImpossible; + case NoneByRolledBack: + return TxnFailureRecovery.ResolveCauseThenRerunLogicAndCommit; + case RollbackFailure: + return TxnFailureRecovery.ResolveCauseAndInconsistency; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case PostCommitFailure: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.ResolveCauseThenRerunPostCommit; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + } + + /** + * Determines the recommended recovery strategy if the intent is to cancel and roll back the + * transaction. + * + * @return the recommended {@link TxnFailureRecovery} action for rolling back + */ + public TxnFailureRecovery recoveryForRollback() { + switch (this.cause.state) { + case NoneByUncommitted: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.InvestigateBecauseImpossible; + case NoneByRolledBack: + return TxnFailureRecovery.NoActionRequired; + case RollbackFailure: + return TxnFailureRecovery.ResolveCauseAndInconsistency; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case NoneByCommitted: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.ManualRollbackRequired; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case LogicFailure: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.InvestigateBecauseImpossible; + case NoneByRolledBack: + return TxnFailureRecovery.NoActionRequired; + case RollbackFailure: + return TxnFailureRecovery.ResolveCauseAndInconsistency; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case CommitFailure: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.InvestigateBecauseImpossible; + case NoneByRolledBack: + return TxnFailureRecovery.NoActionRequired; + case RollbackFailure: + return TxnFailureRecovery.ResolveCauseAndInconsistency; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + case PostCommitFailure: + switch (this.rollback.state) { + case NoneByNotRolledBack: + return TxnFailureRecovery.ManualRollbackRequired; + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + default: + return TxnFailureRecovery.InvestigateBecauseImpossible; + } + } +} diff --git a/src/main/java/com/github/sttk/sabi/TxnFailureRollback.java b/src/main/java/com/github/sttk/sabi/TxnFailureRollback.java new file mode 100644 index 0000000..64b776a --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/TxnFailureRollback.java @@ -0,0 +1,47 @@ +/* + * TxnFailureRollback.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +import com.github.sttk.errs.Err; + +/** + * Represents the rollback outcome of a data connection during transaction failure processing. + * + *

This class encapsulates the state of the rollback (via {@link TxnFailureRollbackState}) and + * any error ({@link Err}) encountered when attempting to roll back the connection. + */ +public class TxnFailureRollback { + + /** The transaction failure rollback state indicating the outcome of the rollback. */ + public final TxnFailureRollbackState state; + + /** The error instance associated with a rollback failure, or {@code null} if none. */ + public final Err err; + + /** + * Constructs a new {@code TxnFailureRollback} with the specified rollback state and error. + * + * @param state the transaction failure rollback state + * @param err the error associated with the rollback failure, or {@code null} + */ + public TxnFailureRollback(TxnFailureRollbackState state, Err err) { + this.state = state; + this.err = err; + } + + /** + * Returns a string representation of this transaction failure rollback outcome. + * + * @return a formatted string containing the rollback state and error details + */ + public String toString() { + var buf = new StringBuilder("{"); + buf.append("State:").append(this.state); + buf.append(" "); + buf.append("Err:").append(this.err); + buf.append("}"); + return buf.toString(); + } +} diff --git a/src/main/java/com/github/sttk/sabi/TxnFailureRollbackState.java b/src/main/java/com/github/sttk/sabi/TxnFailureRollbackState.java new file mode 100644 index 0000000..c37ea80 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/TxnFailureRollbackState.java @@ -0,0 +1,18 @@ +/* + * TxnFailureRollbackState.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi; + +/** Defines the states representing outcomes of transaction rollback attempts. */ +public enum TxnFailureRollbackState { + + /** Indicates that the connection was successfully rolled back without error. */ + NoneByRolledBack, + + /** Indicates that no rollback was performed on the connection. */ + NoneByNotRolledBack, + + /** Indicates that an error occurred while attempting to roll back the connection. */ + RollbackFailure, +} diff --git a/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java b/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java index 10fa9a3..1f74c9a 100644 --- a/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java +++ b/src/main/java/com/github/sttk/sabi/internal/AsyncGroupImpl.java @@ -4,107 +4,82 @@ */ package com.github.sttk.sabi.internal; +import static com.github.sttk.sabi.AsyncGroup.RunnerInterrupted; + import com.github.sttk.errs.Err; import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.ErrEntry; import com.github.sttk.sabi.Runner; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; public final class AsyncGroupImpl implements AsyncGroup { - private ErrEntry errHead; - private ErrEntry errLast; + private List eeList = new ArrayList<>(); private VthEntry vthHead; private VthEntry vthLast; - String name; + int _index; + String _name; - public AsyncGroupImpl() {} + AsyncGroupImpl() {} @Override - public void add(final Runner runner) { - final var name = this.name; + public void add(Runner runner) { + var index = this._index; + var name = this._name; var vth = Thread.ofVirtual() .start( () -> { try { runner.run(); - } catch (Err e) { - addErr(name, e); + } catch (Err err) { + addErr(index, name, err); } catch (RuntimeException e) { - addErr(name, new Err(new AsyncGroup.RuntimeExceptionOccurred(), e)); + addErr(index, name, e); } }); - var ent = new VthEntry(name, vth); + var ve = new VthEntry(index, name, vth); if (this.vthLast == null) { - this.vthHead = ent; - this.vthLast = ent; + this.vthHead = ve; + this.vthLast = ve; } else { - this.vthLast.next = ent; - this.vthLast = ent; + this.vthLast.next = ve; + this.vthLast = ve; } } - synchronized void addErr(String name, Err err) { - var ent = new ErrEntry(name, err); - - if (this.errLast == null) { - this.errHead = ent; - this.errLast = ent; - } else { - this.errLast.next = ent; - this.errLast = ent; - } + synchronized void addErr(int index, String name, Err err) { + var ee = new ErrEntry(index, name, err); + this.eeList.add(ee); } - void joinAndPutErrsInto(Map errMap) { - for (var ent = this.vthHead; ent != null; ent = ent.next) { - try { - ent.thread.join(); - } catch (InterruptedException e) { - addErr(ent.name, new Err(new RunnerInterrupted(), e)); - } - } - for (var ent = this.errHead; ent != null; ent = ent.next) { - errMap.put(ent.name, ent.err); - } - clear(); + synchronized void addErr(int index, String name, RuntimeException e) { + var err = new Err(new AsyncGroup.RuntimeExceptionOccured(), e); + var ee = new ErrEntry(index, name, err); + this.eeList.add(ee); } - void joinAndIgnoreErrs() { - for (var ent = this.vthHead; ent != null; ent = ent.next) { + List join() { + for (var ve = this.vthHead; ve != null; ve = ve.next) { try { - ent.thread.join(); + ve.thread.join(); } catch (InterruptedException e) { + addErr(ve.index, ve.name, new Err(new RunnerInterrupted(), e)); } } - clear(); - } - - void clear() { - this.errHead = null; - this.errLast = null; - this.vthHead = null; - this.vthLast = null; - } -} - -class ErrEntry { - final String name; - final Err err; - ErrEntry next; - - ErrEntry(String name, Err err) { - this.name = name; - this.err = err; + return this.eeList; } } class VthEntry { + final int index; final String name; final Thread thread; VthEntry next; - VthEntry(String name, Thread thread) { + VthEntry(int index, String name, Thread thread) { + this.index = index; 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 index e3bc666..ac2eed9 100644 --- a/src/main/java/com/github/sttk/sabi/internal/DataConnContainer.java +++ b/src/main/java/com/github/sttk/sabi/internal/DataConnContainer.java @@ -1,16 +1,14 @@ /* * DataConnContainer.java - * Copyright (C) 2025-2026 Takayuki Sato. All Rights Reserved. + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. */ package com.github.sttk.sabi.internal; import com.github.sttk.sabi.DataConn; -public final class DataConnContainer { - DataConnContainer prev; - DataConnContainer next; - String name; - DataConn conn; +public class DataConnContainer { + final String name; + final DataConn conn; DataConnContainer(String name, DataConn conn) { this.name = name; diff --git a/src/main/java/com/github/sttk/sabi/internal/DataConnList.java b/src/main/java/com/github/sttk/sabi/internal/DataConnList.java deleted file mode 100644 index c897da5..0000000 --- a/src/main/java/com/github/sttk/sabi/internal/DataConnList.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * DataConnList.java - * Copyright (C) 2025-2026 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.internal; - -public final 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/DataConnManager.java b/src/main/java/com/github/sttk/sabi/internal/DataConnManager.java new file mode 100644 index 0000000..fe28af1 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataConnManager.java @@ -0,0 +1,219 @@ +/* + * DataConnManager.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import static com.github.sttk.sabi.DataConn.FailToCommitDataConn; +import static com.github.sttk.sabi.DataConn.FailToPostCommitDataConn; +import static com.github.sttk.sabi.DataConn.FailToPreCommitDataConn; + +import com.github.sttk.errs.Err; +import com.github.sttk.sabi.TxnFailureCause; +import com.github.sttk.sabi.TxnFailureCauseState; +import com.github.sttk.sabi.TxnFailureRollback; +import com.github.sttk.sabi.TxnFailureRollbackState; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DataConnManager { + final ArrayList list; + final Map indexMap; + boolean committed; + + DataConnManager() { + this.list = new ArrayList<>(); + this.indexMap = new HashMap<>(); + } + + DataConnManager(List names) { + this.list = new ArrayList<>(names.size()); + this.indexMap = new HashMap<>(names.size()); + + // Because earlier ones take precedence when names overlap + for (int i = names.size() - 1; i >= 0; i--) { + this.indexMap.put(names.get(i), i); + this.list.add(new DataConnContainer(names.get(i), null)); + } + } + + void add(DataConnContainer cont) { + var idx = this.indexMap.get(cont.name); + if (idx != null) { + // Because earlier ones take precedence when names overlap + if (this.list.get(idx).conn == null) { + this.list.set(idx, cont); + } + } else { + this.indexMap.put(cont.name, this.list.size()); + this.list.add(cont); + } + } + + void prepareTxnFailureReportBuilders(ArrayList list) { + list.ensureCapacity(this.indexMap.size()); + for (var cont : this.list) { + if (cont.conn != null) { + list.add(new TxnFailureReportBuilder(cont.name, cont.conn.getClass())); + } + } + } + + void commit(List builders) throws Err { + var ag = new AsyncGroupImpl(); + int ii = 0; + for (var cont : this.list) { + if (cont.conn == null) { + continue; + } + ag._name = cont.name; + ag._index = ii; + ii++; + try { + cont.conn.preCommit(ag); + } catch (Err err) { + ag.addErr(ag._index, ag._name, err); + break; + } catch (RuntimeException re) { + ag.addErr(ag._index, ag._name, re); + break; + } + } + var errors = ag.join(); + + if (!errors.isEmpty()) { + for (var ee : errors) { + int idx = ee.index; + builders.get(idx).cause = new TxnFailureCause(TxnFailureCauseState.LogicFailure, ee.err); + } + throw new Err(new FailToPreCommitDataConn(errors)); + } + + ag = new AsyncGroupImpl(); + ii = 0; + for (var cont : this.list) { + if (cont.conn == null) { + continue; + } + ag._name = cont.name; + ag._index = ii; + ii++; + if (!cont.conn.isCommitted()) { + try { + cont.conn.commit(ag); + } catch (Err err) { + ag.addErr(ag._index, ag._name, err); + break; + } catch (RuntimeException re) { + ag.addErr(ag._index, ag._name, re); + break; + } + } + } + errors = ag.join(); + + if (!errors.isEmpty()) { + for (var ee : errors) { + int idx = ee.index; + builders.get(idx).cause = new TxnFailureCause(TxnFailureCauseState.CommitFailure, ee.err); + } + throw new Err(new FailToCommitDataConn(errors)); + } + + this.committed = true; + + ag = new AsyncGroupImpl(); + ii = 0; + for (var cont : this.list) { + if (cont.conn == null) { + continue; + } + ag._name = cont.name; + ag._index = ii; + ii++; + try { + cont.conn.postCommit(ag); + } catch (Err err) { + ag.addErr(ag._index, ag._name, err); + // don't break + } catch (RuntimeException re) { + ag.addErr(ag._index, ag._name, re); + // don't break + } + } + errors = ag.join(); + + if (!errors.isEmpty()) { + for (var ee : errors) { + int idx = ee.index; + builders.get(idx).cause = + new TxnFailureCause(TxnFailureCauseState.PostCommitFailure, ee.err); + } + throw new Err(new FailToPostCommitDataConn(errors)); + } + } + + void rollback(List builders) { + var ag = new AsyncGroupImpl(); + int ii = 0; + for (var cont : this.list) { + if (cont.conn == null) { + continue; + } + ag._name = cont.name; + ag._index = ii; + ii++; + if (cont.conn.isCommitted()) { + if (builders.get(ag._index).cause.state == TxnFailureCauseState.NoneByUncommitted) { + builders.get(ag._index).cause = + new TxnFailureCause(TxnFailureCauseState.NoneByCommitted, null); + } + continue; + } + if (this.committed) { + continue; + } + try { + cont.conn.rollback(ag); + builders.get(ag._index).rollback = + new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + } catch (Err err) { + ag.addErr(ag._index, ag._name, err); + } catch (RuntimeException re) { + ag.addErr(ag._index, ag._name, re); + } + } + var errors = ag.join(); + + if (!errors.isEmpty()) { + for (var ee : errors) { + int idx = ee.index; + builders.get(idx).rollback = + new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, ee.err); + } + } + + ag = new AsyncGroupImpl(); + for (var cont : this.list) { + if (cont.conn != null) { + var reports = builders.stream().map(TxnFailureReportBuilder::build).toList(); + cont.conn.onTxnFailure(ag, reports); + } + } + ag.join(); + } + + void close() { + this.indexMap.clear(); + + for (int i = this.list.size() - 1; i >= 0; i--) { + var cont = this.list.get(i); + if (cont.conn != null) { + cont.conn.close(); + } + } + this.list.clear(); + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java b/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java index 6fea032..704c79b 100644 --- a/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java +++ b/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java @@ -4,32 +4,50 @@ */ package com.github.sttk.sabi.internal; +import static com.github.sttk.sabi.DataHub.CreatedDataConnIsNull; +import static com.github.sttk.sabi.DataHub.FailToCastDataConn; +import static com.github.sttk.sabi.DataHub.FailToCreateDataConn; +import static com.github.sttk.sabi.DataHub.FailToSetupGlobalDataSrcs; +import static com.github.sttk.sabi.DataHub.FailToSetupLocalDataSrcs; +import static com.github.sttk.sabi.DataHub.NoDataSrcToCreateDataConn; + import com.github.sttk.errs.Err; import com.github.sttk.sabi.DataConn; -import com.github.sttk.sabi.DataHub; import com.github.sttk.sabi.DataSrc; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; public final class DataHubInner { - - static final DataSrcList GLOBAL_DATA_SRC_LIST = new DataSrcList(false); + static final DataSrcManager GLOBAL_DATA_SRC_MANAGER = new DataSrcManager(false); static final AtomicBoolean GLOBAL_DATA_SRCS_FIXED = new AtomicBoolean(false); - public static void usesGlobal(String name, DataSrc ds) { + public static void useGlobal(String name, DataSrc ds) { if (!GLOBAL_DATA_SRCS_FIXED.get()) { - GLOBAL_DATA_SRC_LIST.addDataSrc(name, ds); + GLOBAL_DATA_SRC_MANAGER.add(name, ds); } } public static AutoCloseable setupGlobals() throws Err { if (GLOBAL_DATA_SRCS_FIXED.compareAndSet(false, true)) { - var errMap = GLOBAL_DATA_SRC_LIST.setupDataSrcs(); - if (!errMap.isEmpty()) { - GLOBAL_DATA_SRC_LIST.closeDataSrcs(); - throw new Err(new DataHub.FailToSetupGlobalDataSrcs(errMap)); + var errors = GLOBAL_DATA_SRC_MANAGER.setup(); + if (!errors.isEmpty()) { + GLOBAL_DATA_SRC_MANAGER.close(); + throw new Err(new FailToSetupGlobalDataSrcs(errors)); + } + } + return new AutoShutdown(); + } + + public static AutoCloseable setupGlobalsWithOrder(List names) throws Err { + if (GLOBAL_DATA_SRCS_FIXED.compareAndSet(false, true)) { + var errors = GLOBAL_DATA_SRC_MANAGER.setupWithOrder(names); + if (!errors.isEmpty()) { + GLOBAL_DATA_SRC_MANAGER.close(); + throw new Err(new FailToSetupGlobalDataSrcs(errors)); } } return new AutoShutdown(); @@ -38,176 +56,139 @@ public static AutoCloseable setupGlobals() throws Err { static class AutoShutdown implements AutoCloseable { @Override public void close() { - GLOBAL_DATA_SRC_LIST.closeDataSrcs(); + GLOBAL_DATA_SRC_MANAGER.close(); } } - final DataSrcList localDataSrcList = new DataSrcList(true); - final Map dataSrcMap = new HashMap<>(); - final DataConnList dataConnList = new DataConnList(); - final Map dataConnMap = new HashMap<>(); - boolean fixed = false; + final DataSrcManager localDataSrcManager; + final Map dataSrcMap; + final DataConnManager dataConnManager; + final Map dataConnMap; + boolean fixed; public DataHubInner() { GLOBAL_DATA_SRCS_FIXED.compareAndSet(false, true); - GLOBAL_DATA_SRC_LIST.copyContainerPtrsDidSetupInto(this.dataSrcMap); + this.fixed = false; + + this.localDataSrcManager = new DataSrcManager(true); + this.dataSrcMap = new HashMap<>(); + this.dataConnManager = new DataConnManager(); + this.dataConnMap = new HashMap<>(); + + GLOBAL_DATA_SRC_MANAGER.copyDsReadyToMap(this.dataSrcMap); } - public void uses(String name, DataSrc ds) { + public DataHubInner(List names) { + GLOBAL_DATA_SRCS_FIXED.compareAndSet(false, true); + this.fixed = false; + + this.localDataSrcManager = new DataSrcManager(true); + this.dataSrcMap = new HashMap<>(); + this.dataConnManager = new DataConnManager(names); + this.dataConnMap = new HashMap<>(); + + GLOBAL_DATA_SRC_MANAGER.copyDsReadyToMap(this.dataSrcMap); + } + + public void useLocal(String name, DataSrc ds) { if (this.fixed) { return; } - this.localDataSrcList.addDataSrc(name, ds); + this.localDataSrcManager.add(name, ds); } - public void disuses(String name) { + public void disuseLocal(String name) { if (this.fixed) { return; } - var ptr = this.dataSrcMap.get(name); - if (ptr != null && ptr.local && Objects.equals(ptr.name, name)) { + var cont = this.dataSrcMap.get(name); + if (cont != null && cont.local && Objects.equals(cont.name, name)) { this.dataSrcMap.remove(name); } - this.localDataSrcList.removeAndCloseContainerPtrDidSetupByName(name); - this.localDataSrcList.removeAndCloseContainerPtrNotSetupByName(name); + this.localDataSrcManager.remove(name); } - public void close() { + public void closeLocals() { this.dataConnMap.clear(); - this.dataConnList.closeDataConns(); + this.dataConnManager.close(); this.dataSrcMap.clear(); - this.localDataSrcList.closeDataSrcs(); + this.localDataSrcManager.close(); } public void begin() throws Err { this.fixed = true; - var errMap = this.localDataSrcList.setupDataSrcs(); - this.localDataSrcList.copyContainerPtrsDidSetupInto(this.dataSrcMap); + var errors = this.localDataSrcManager.setup(); + this.localDataSrcManager.copyDsReadyToMap(this.dataSrcMap); - if (!errMap.isEmpty()) { - throw new Err(new DataHub.FailToSetupLocalDataSrcs(errMap)); + if (!errors.isEmpty()) { + throw new Err(new FailToSetupLocalDataSrcs(errors)); } } - public void commit() throws Err { - var errMap = new HashMap(); - - var ag = new AsyncGroupImpl(); - var ptr = this.dataConnList.head; - while (ptr != null) { - ag.name = ptr.name; - try { - ptr.conn.preCommit(ag); - } catch (Err e) { - errMap.put(ptr.name, e); - break; - } catch (RuntimeException e) { - errMap.put(ptr.name, new Err(new DataHub.RuntimeExceptionOccurred(), e)); - break; - } - ptr = ptr.next; - } - ag.joinAndPutErrsInto(errMap); - - if (!errMap.isEmpty()) { - throw new Err(new DataHub.FailToPreCommitDataConn(errMap)); - } - - ag = new AsyncGroupImpl(); - ptr = this.dataConnList.head; - while (ptr != null) { - ag.name = ptr.name; - try { - ptr.conn.commit(ag); - } catch (Err e) { - errMap.put(ptr.name, e); - break; - } catch (RuntimeException e) { - errMap.put(ptr.name, new Err(new DataHub.RuntimeExceptionOccurred(), e)); - break; - } - ptr = ptr.next; - } - ag.joinAndPutErrsInto(errMap); - - if (!errMap.isEmpty()) { - throw new Err(new DataHub.FailToCommitDataConn(errMap)); - } - - ag = new AsyncGroupImpl(); - ptr = this.dataConnList.head; - while (ptr != null) { - ag.name = ptr.name; - ptr.conn.postCommit(ag); - ptr = ptr.next; - } - - ag.joinAndIgnoreErrs(); + public void prepareTxnFailureReportBuilders(ArrayList list) { + this.dataConnManager.prepareTxnFailureReportBuilders(list); } - 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; - } + public void commit(List builders) throws Err { + this.dataConnManager.commit(builders); + } - ag.joinAndIgnoreErrs(); + public void rollback(List builders) throws Err { + this.dataConnManager.rollback(builders); } public void end() { this.dataConnMap.clear(); - this.dataConnList.closeDataConns(); + this.dataConnManager.close(); + this.fixed = false; } public C getDataConn(String name, Class cls) throws Err { - var connPtr = this.dataConnMap.get(name); - if (connPtr != null) { + var dcCont = this.dataConnMap.get(name); + if (dcCont != null && dcCont.conn != null) { try { - return cls.cast(connPtr.conn); + return cls.cast(dcCont.conn); } catch (Exception e) { - throw new Err(new DataHub.FailToCastDataConn(name, cls.getName()), e); + String fromType = dcCont.conn.getClass().getName(); + String toType = cls.getName(); + throw new Err(new FailToCastDataConn(name, fromType, toType), e); } } - var dsPtr = this.dataSrcMap.get(name); - if (dsPtr == null) { - throw new Err(new DataHub.NoDataSrcToCreateDataConn(name, cls.getName())); + var dsCont = this.dataSrcMap.get(name); + if (dsCont == null || dsCont.ds == null) { + throw new Err(new NoDataSrcToCreateDataConn(name, cls.getName())); } - DataConn conn; + DataConn dc; try { - conn = dsPtr.ds.createDataConn(); + dc = dsCont.ds.createDataConn(); } catch (Exception e) { - throw new Err(new DataHub.FailToCreateDataConn(name, cls.getName()), e); + throw new Err(new FailToCreateDataConn(name, cls.getName()), e); } - if (conn == null) { - throw new Err(new DataHub.CreatedDataConnIsNull(name, cls.getName())); + if (dc == null) { + throw new Err(new CreatedDataConnIsNull(name, cls.getName())); } + dcCont = new DataConnContainer(name, dc); + this.dataConnMap.put(name, dcCont); + this.dataConnManager.add(dcCont); + C c; try { - c = cls.cast(conn); + c = cls.cast(dc); } catch (Exception e) { - throw new Err(new DataHub.FailToCastDataConn(name, cls.getName()), e); + String fromType = dc.getClass().getName(); + String toType = cls.getName(); + throw new Err(new FailToCastDataConn(name, fromType, toType), 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 index 23fc32f..90e413b 100644 --- a/src/main/java/com/github/sttk/sabi/internal/DataSrcContainer.java +++ b/src/main/java/com/github/sttk/sabi/internal/DataSrcContainer.java @@ -1,16 +1,14 @@ /* * DataSrcContainer.java - * Copyright (C) 2025-2026 Takayuki Sato. All Rights Reserved. + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. */ package com.github.sttk.sabi.internal; import com.github.sttk.sabi.DataSrc; -public final class DataSrcContainer { - DataSrcContainer prev; - DataSrcContainer next; - boolean local; - String name; +public class DataSrcContainer { + final boolean local; + final String name; DataSrc ds; DataSrcContainer(boolean local, String name, DataSrc 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 deleted file mode 100644 index 99ebc81..0000000 --- a/src/main/java/com/github/sttk/sabi/internal/DataSrcList.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * DataSrcList.java - * Copyright (C) 2025-2026 Takayuki Sato. All Rights Reserved. - */ -package com.github.sttk.sabi.internal; - -import com.github.sttk.errs.Err; -import com.github.sttk.sabi.DataHub; -import com.github.sttk.sabi.DataSrc; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public final 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 errMap = new HashMap(); - - if (this.notSetupHead == null) { - return errMap; - } - - var ag = new AsyncGroupImpl(); - - var ptr = this.notSetupHead; - while (ptr != null) { - ag.name = ptr.name; - try { - ptr.ds.setup(ag); - } catch (Err err) { - errMap.put(ptr.name, err); - break; - } catch (RuntimeException e) { - errMap.put(ptr.name, new Err(new DataHub.RuntimeExceptionOccurred(), e)); - break; - } - ptr = ptr.next; - } - - ag.joinAndPutErrsInto(errMap); - - var firstPtrNotSetupYet = ptr; - - ptr = this.notSetupHead; - while (ptr != null && ptr != firstPtrNotSetupYet) { - var next = ptr.next; - if (!errMap.containsKey(ptr.name)) { - this.removeContainerPtrNotSetup(ptr); - this.appendContainerPtrDidSetup(ptr); - } - ptr = next; - } - - return errMap; - } - - 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/com/github/sttk/sabi/internal/DataSrcManager.java b/src/main/java/com/github/sttk/sabi/internal/DataSrcManager.java new file mode 100644 index 0000000..3321d35 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/DataSrcManager.java @@ -0,0 +1,202 @@ +/* + * DataSrcManager.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.errs.Err; +import com.github.sttk.sabi.DataSrc; +import com.github.sttk.sabi.ErrEntry; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class DataSrcManager { + final boolean local; + final List listUnready; + final List listReady; + + DataSrcManager(boolean local) { + this.local = local; + this.listUnready = new ArrayList<>(0); + this.listReady = new ArrayList<>(0); + } + + void add(String name, DataSrc ds) { + this.listUnready.add(new DataSrcContainer(this.local, name, ds)); + } + + void remove(String name) { + for (var cont : this.listReady) { + if (Objects.equals(cont.name, name) && cont.ds != null) { + cont.ds.close(); + cont.ds = null; + } + } + for (var cont : this.listUnready) { + if (Objects.equals(cont.name, name) && cont.ds != null) { + cont.ds = null; + } + } + } + + void close() { + for (int i = this.listReady.size() - 1; i >= 0; i--) { + var cont = this.listReady.get(i); + if (cont.ds != null) { + cont.ds.close(); + cont.ds = null; + } + } + for (int i = this.listUnready.size() - 1; i >= 0; i--) { + var cont = this.listUnready.get(i); + if (cont.ds != null) { + cont.ds = null; + } + } + this.listReady.clear(); + this.listUnready.clear(); + } + + List setup() { + if (this.listUnready.isEmpty()) { + return Collections.emptyList(); + } + + var ag = new AsyncGroupImpl(); + int ii = 0, nDone = 0; + for (int i = 0, n = this.listUnready.size(); i < n; i++) { + var cont = this.listUnready.get(i); + if (cont == null || cont.ds == null) { + continue; + } + ag._name = cont.name; + ag._index = ii; + ii++; + try { + cont.ds.setup(ag); + } catch (Err err) { + ag.addErr(ag._index, ag._name, err); + nDone = i; + break; + } catch (RuntimeException re) { + ag.addErr(ag._index, ag._name, re); + nDone = i; + break; + } + } + var errors = ag.join(); + + if (errors.isEmpty()) { + for (int i = 0, n = this.listUnready.size(); i < n; i++) { + var cont = this.listUnready.get(i); + if (cont.ds != null) { + this.listReady.add(cont); + } + } + this.listUnready.clear(); + return Collections.emptyList(); + } else { + for (int i = nDone - 1; i >= 0; i--) { + var cont = this.listUnready.get(i); + if (cont.ds != null) { + cont.ds.close(); + } + } + return errors; + } + } + + List setupWithOrder(List names) { + if (this.listUnready.isEmpty()) { + return Collections.emptyList(); + } + + var indexedMap = new HashMap(names.size()); + // Becuase earlier ones take precedence when names overlap + for (int i = names.size() - 1; i >= 0; i--) { + indexedMap.put(names.get(i), i); + } + + var orderedIndexes = new ArrayList(this.listUnready.size()); + for (int i = 0, n = names.size(); i < n; i++) { + orderedIndexes.add(null); // null indicates unset + } + + for (int listIndex = 0, n = this.listUnready.size(); listIndex < n; listIndex++) { + var cont = this.listUnready.get(listIndex); + if (cont != null && cont.ds != null) { + Integer orderIndex = indexedMap.get(cont.name); + if (orderIndex != null) { + orderedIndexes.set(orderIndex, listIndex); + indexedMap.remove(cont.name); + } else { + orderedIndexes.add(listIndex); + } + } + } + + var ag = new AsyncGroupImpl(); + int ii = 0, nDone = 0; + for (int orderIndex = 0, n = orderedIndexes.size(); orderIndex < n; orderIndex++) { + Integer listIndex = orderedIndexes.get(orderIndex); + if (listIndex == null) { // ignore unset + continue; + } + var cont = this.listUnready.get(listIndex); + if (cont == null || cont.ds == null) { + continue; + } + ag._name = cont.name; + ag._index = ii; + ii++; + try { + cont.ds.setup(ag); + } catch (Err err) { + ag.addErr(ag._index, ag._name, err); + nDone = orderIndex; + break; + } catch (RuntimeException re) { + ag.addErr(ag._index, ag._name, re); + nDone = orderIndex; + break; + } + } + var errors = ag.join(); + + if (errors.isEmpty()) { + for (Integer listIndex : orderedIndexes) { + if (listIndex == null) { // ignore unset + continue; + } + var cont = this.listUnready.get(listIndex); + if (cont == null || cont.ds == null) { + continue; + } + this.listReady.add(cont); + } + this.listUnready.clear(); + return Collections.emptyList(); + } else { + for (int orderIndex = nDone - 1; orderIndex >= 0; orderIndex--) { + Integer listIndex = orderedIndexes.get(orderIndex); + if (listIndex != null) { // Ignore unset + var cont = this.listUnready.get(listIndex); + if (cont.ds != null) { + cont.ds.close(); + } + } + } + return errors; + } + } + + void copyDsReadyToMap(Map contMap) { + for (var cont : this.listReady) { + contMap.put(cont.name, cont); + } + } +} diff --git a/src/main/java/com/github/sttk/sabi/internal/TxnFailureReportBuilder.java b/src/main/java/com/github/sttk/sabi/internal/TxnFailureReportBuilder.java new file mode 100644 index 0000000..a5c8db6 --- /dev/null +++ b/src/main/java/com/github/sttk/sabi/internal/TxnFailureReportBuilder.java @@ -0,0 +1,31 @@ +/* + * TxnFailureReport.java + * Copyright (C) 2026 Takayuki Sato. All Rights Reserved. + */ +package com.github.sttk.sabi.internal; + +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.TxnFailureCause; +import com.github.sttk.sabi.TxnFailureCauseState; +import com.github.sttk.sabi.TxnFailureReport; +import com.github.sttk.sabi.TxnFailureRollback; +import com.github.sttk.sabi.TxnFailureRollbackState; + +public class TxnFailureReportBuilder { + String dataConnName; + Class dataConnClass; + TxnFailureCause cause; + TxnFailureRollback rollback; + + TxnFailureReportBuilder(String name, Class cls) { + this.dataConnName = name; + this.dataConnClass = cls; + this.cause = new TxnFailureCause(TxnFailureCauseState.NoneByUncommitted, null); + this.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + } + + TxnFailureReport build() { + return new TxnFailureReport( + this.dataConnName, this.dataConnClass.getName(), this.cause, this.rollback); + } +} diff --git a/src/main/java/com/github/sttk/sabi/package-info.java b/src/main/java/com/github/sttk/sabi/package-info.java index 02a0eae..50616de 100644 --- a/src/main/java/com/github/sttk/sabi/package-info.java +++ b/src/main/java/com/github/sttk/sabi/package-info.java @@ -4,7 +4,7 @@ */ /** - * Provides classes and interfaces of sabi framework. + * Provides core interfaces and classes for the sabi framework. * * @version 0.8 */ diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ecced99..3eaf209 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -4,10 +4,11 @@ */ /** - * Defines the APIs of Sabi framework. + * Defines the module APIs of the 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. + *

This module provides interfaces and classes to abstract data access to external data stores, + * manage data source and connection lifecycles, and execute logic functions in transactional or + * non-transactional scopes. * * @version 0.8 */ diff --git a/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java b/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java index 09e4d11..2a7098e 100644 --- a/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java +++ b/src/test/java/com/github/sttk/sabi/internal/AsyncGroupImplTest.java @@ -1,11 +1,10 @@ package com.github.sttk.sabi.internal; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.fail; import com.github.sttk.errs.Err; import com.github.sttk.sabi.Runner; -import java.util.HashMap; import org.junit.jupiter.api.Test; public class AsyncGroupImplTest { @@ -15,9 +14,8 @@ private AsyncGroupImplTest() {} void zero() { var ag = new AsyncGroupImpl(); - var m = new HashMap(); - ag.joinAndPutErrsInto(m); - assertThat(m).hasSize(0); + var errors = ag.join(); + assertThat(errors).isEmpty(); } @Test @@ -34,14 +32,14 @@ void ok() { executed[0] = true; }; - ag.name = "foo"; + ag._index = 123; + ag._name = "foo"; ag.add(fn); assertThat(executed[0]).isFalse(); - var m = new HashMap(); - ag.joinAndPutErrsInto(m); - assertThat(m).hasSize(0); + var errors = ag.join(); assertThat(executed[0]).isTrue(); + assertThat(errors).isEmpty(); } @Test @@ -61,23 +59,25 @@ record FailToDoSomething() {} throw new Err(new FailToDoSomething()); }; - ag.name = "foo"; + ag._index = 123; + ag._name = "foo"; ag.add(fn); assertThat(executed[0]).isFalse(); - var m = new HashMap(); - ag.joinAndPutErrsInto(m); - assertThat(m).hasSize(1); + var errors = ag.join(); assertThat(executed[0]).isTrue(); + assertThat(errors).hasSize(1); - switch (m.get("foo").getReason()) { + assertThat(errors.get(0).index).isEqualTo(123); + assertThat(errors.get(0).name).isEqualTo("foo"); + switch (errors.get(0).err.getReason()) { case FailToDoSomething r -> {} default -> fail(); } } @Test - void multiple_errors_with_an_error_map() { + void multiple_errors() { var ag = new AsyncGroupImpl(); record Reason0() {} @@ -89,7 +89,7 @@ record Reason2() {} Runner fn0 = () -> { try { - Thread.sleep(50); + Thread.sleep(200); } catch (Exception e) { } executed[0] = true; @@ -98,7 +98,7 @@ record Reason2() {} Runner fn1 = () -> { try { - Thread.sleep(50); + Thread.sleep(400); } catch (Exception e) { } executed[1] = true; @@ -114,79 +114,35 @@ record Reason2() {} throw new Err(new Reason2()); }; - ag.name = "foo0"; + ag._index = 123; + ag._name = "foo0"; ag.add(fn0); - ag.name = "foo1"; + ag._index = 456; + ag._name = "foo1"; ag.add(fn1); - ag.name = "foo2"; + ag._index = 789; + ag._name = "foo2"; ag.add(fn2); - var m = new HashMap(); - ag.joinAndPutErrsInto(m); - assertThat(m).hasSize(3); + var errors = ag.join(); assertThat(executed[0]).isTrue(); assertThat(executed[1]).isTrue(); assertThat(executed[2]).isTrue(); - assertThat(m.get("foo0").toString()) + assertThat(errors.get(0).index).isEqualTo(789); + assertThat(errors.get(0).name).isEqualTo("foo2"); + assertThat(errors.get(0).err.toString()) + .isEqualTo( + "com.github.sttk.errs.Err { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason2 Reason2[], file = AsyncGroupImplTest.java, line = 114 }"); + assertThat(errors.get(1).index).isEqualTo(123); + assertThat(errors.get(1).name).isEqualTo("foo0"); + assertThat(errors.get(1).err.toString()) .isEqualTo( "com.github.sttk.errs.Err { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason0 Reason0[], file = AsyncGroupImplTest.java, line = 96 }"); - assertThat(m.get("foo1").toString()) + assertThat(errors.get(2).index).isEqualTo(456); + assertThat(errors.get(2).name).isEqualTo("foo1"); + assertThat(errors.get(2).err.toString()) .isEqualTo( "com.github.sttk.errs.Err { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason1 Reason1[], file = AsyncGroupImplTest.java, line = 105 }"); - assertThat(m.get("foo2").toString()) - .isEqualTo( - "com.github.sttk.errs.Err { reason = com.github.sttk.sabi.internal.AsyncGroupImplTest$1Reason2 Reason2[], file = AsyncGroupImplTest.java, line = 114 }"); - } - - @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 Err(new Reason0()); - }; - Runner fn1 = - () -> { - try { - Thread.sleep(50); - } catch (Exception e) { - } - executed[1] = true; - throw new Err(new Reason1()); - }; - Runner fn2 = - () -> { - try { - Thread.sleep(50); - } catch (Exception e) { - } - executed[2] = true; - throw new Err(new Reason2()); - }; - - ag.name = "foo0"; - ag.add(fn0); - ag.name = "foo1"; - ag.add(fn1); - ag.name = "foo2"; - ag.add(fn2); - - ag.joinAndIgnoreErrs(); - 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 index a269a94..4b210d1 100644 --- a/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java +++ b/src/test/java/com/github/sttk/sabi/internal/DataAccTest.java @@ -1,5 +1,7 @@ package com.github.sttk.sabi.internal; +import static com.github.sttk.sabi.Sabi.setup; +import static com.github.sttk.sabi.Sabi.uses; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; @@ -10,7 +12,7 @@ import com.github.sttk.sabi.DataHub; import com.github.sttk.sabi.DataSrc; import com.github.sttk.sabi.Logic; -import com.github.sttk.sabi.Sabi; +import com.github.sttk.sabi.TxnFailureReport; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.AfterEach; @@ -19,213 +21,192 @@ import org.junit.jupiter.api.Test; 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 Err { - if (this.willFail) { - this.logger.add(String.format("FooDataSrc %d failed to setup", this.id)); - throw new Err("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 Err { - this.logger.add(String.format("FooDataSrc %d created FooDataConn", this.id)); - return new FooDataConn(this.id, this.text, this.logger); - } - } + private DataAccTest() {} static class FooDataConn implements DataConn { - private int id; - private String text; - private boolean committed; - private List logger; + final int id; + final String text; + boolean committed; + List logger; - FooDataConn(int id, String text, List logger) { + FooDataConn(int id, String s, List logger) { this.id = id; - this.text = text; + this.text = s; this.logger = logger; + logger.add(String.format("FooDataConn#new %d", id)); } - String getText() { + public String getText() { + this.logger.add(String.format("FooDataConn#getText %d", this.id)); return this.text; } @Override public void commit(AsyncGroup ag) throws Err { this.committed = true; - this.logger.add(String.format("FooDataConn %d committed", this.id)); + this.logger.add(String.format("FooDataConn#commit %d", this.id)); } @Override public void preCommit(AsyncGroup ag) throws Err { - this.logger.add(String.format("FooDataConn %d pre committed", this.id)); + this.logger.add(String.format("FooDataConn#preCommit %d", this.id)); } @Override - public void postCommit(AsyncGroup ag) { - this.logger.add(String.format("FooDataConn %d post committed", this.id)); + public void postCommit(AsyncGroup ag) throws Err { + this.logger.add(String.format("FooDataConn#postCommit %d", this.id)); } @Override - public boolean shouldForceBack() { + public boolean isCommitted() { return this.committed; } @Override - public void rollback(AsyncGroup ag) { - this.logger.add(String.format("FooDataConn %d rollbacked", this.id)); + public void rollback(AsyncGroup ag) throws Err { + this.logger.add(String.format("FooDataConn#rollback %d", this.id)); } @Override - public void forceBack(AsyncGroup ag) { - this.logger.add(String.format("FooDataConn %d forced back", this.id)); + public void onTxnFailure(AsyncGroup ag, List reports) { + this.logger.add(String.format("FooDataConn#onTxnFailure %d", this.id)); } @Override public void close() { - this.logger.add(String.format("FooDataConn %d closed", this.id)); + this.logger.add(String.format("FooDataConn#close %d", this.id)); } } - static class BarDataSrc implements DataSrc { - private int id; - private String text; - private List logger; - private boolean willFail; + static class FooDataSrc implements DataSrc { + final int id; + final String text; + boolean failToSetup; + List logger; - BarDataSrc(int id, List logger, boolean willFail) { + FooDataSrc(int id, List logger, boolean fail) { this.id = id; - this.text = null; + this.text = "hello"; this.logger = logger; - this.willFail = willFail; + this.failToSetup = fail; + logger.add(String.format("FooDataSrc#new %d", id)); } @Override public void setup(AsyncGroup ag) throws Err { - if (this.willFail) { - this.logger.add(String.format("BarDataSrc %d failed to setup", this.id)); - throw new Err("XXX"); - } - this.logger.add(String.format("BarDataSrc %d setupped", this.id)); + this.logger.add(String.format("FooDataSrc#setup %d", 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)); + this.logger.add(String.format("FooDataSrc#close %d", this.id)); } @Override public DataConn createDataConn() throws Err { - this.logger.add(String.format("BarDataSrc %d created BarDataConn", this.id)); - return new BarDataConn(this.id, this.text, this.logger, this); + this.logger.add(String.format("FooDataSrc#createDataConn %d", this.id)); + return new FooDataConn(this.id, this.text, this.logger); } } static class BarDataConn implements DataConn { - private int id; - private String text; - private boolean committed; - private List logger; - private BarDataSrc ds; + final int id; + String text; + boolean committed; + List logger; - BarDataConn(int id, String text, List logger, BarDataSrc ds) { + BarDataConn(int id, List logger) { this.id = id; - this.text = text; - this.committed = false; + this.text = ""; this.logger = logger; - this.ds = ds; + logger.add(String.format("BarDataConn#new %d", id)); } - void setText(String s) { - this.text = s; + public void setText(String v) { + logger.add(String.format("BarDataConn#setText %d", this.id)); + this.text = v; } @Override public void commit(AsyncGroup ag) throws Err { this.committed = true; - this.ds.text = this.text; - this.logger.add(String.format("BarDataConn %d committed", this.id)); + this.logger.add(String.format("BarDataConn#commit %d", this.id)); } @Override public void preCommit(AsyncGroup ag) throws Err { - this.logger.add(String.format("BarDataConn %d pre committed", this.id)); + this.logger.add(String.format("BarDataConn#preCommit %d", this.id)); } @Override - public void postCommit(AsyncGroup ag) { - this.logger.add(String.format("BarDataConn %d post committed", this.id)); + public void postCommit(AsyncGroup ag) throws Err { + this.logger.add(String.format("BarDataConn#postCommit %d", this.id)); } @Override - public boolean shouldForceBack() { + public boolean isCommitted() { return this.committed; } @Override - public void rollback(AsyncGroup ag) { - this.logger.add(String.format("BarDataConn %d rollbacked", this.id)); + public void rollback(AsyncGroup ag) throws Err { + this.logger.add(String.format("BarDataConn#rollback %d", this.id)); } @Override - public void forceBack(AsyncGroup ag) { - this.logger.add(String.format("BarDataConn %d forced back", this.id)); + public void onTxnFailure(AsyncGroup ag, List reports) { + this.logger.add(String.format("BarDataConn#onTxnFailure %d", 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)); + this.logger.add(String.format("BarDataConn#close %d", this.id)); } } - /// + static class BarDataSrc implements DataSrc { + final int id; + List logger; + boolean failToSetup; - static interface SampleData { - String getValue() throws Err; + BarDataSrc(int id, List logger, boolean fail) { + this.id = id; + this.logger = logger; + this.failToSetup = fail; + logger.add(String.format("BarDataSrc#new %d", id)); + } - void setValue(String text) throws Err; - } + @Override + public void setup(AsyncGroup ag) throws Err { + this.logger.add(String.format("BarDataSrc#setup %d", this.id)); + } - static class SampleLogic implements Logic { @Override - public void run(SampleData data) throws Err { - var v = data.getValue(); - data.setValue(v); + public void close() { + this.logger.add(String.format("BarDataSrc#close %d", this.id)); } - } - static class FailingLogic implements Logic { @Override - public void run(SampleData data) throws Err { - throw new Err("ZZZ"); + public DataConn createDataConn() throws Err { + this.logger.add(String.format("BarDataSrc#createDataConn %d", this.id)); + return new BarDataConn(this.id, this.logger); } } + static interface SampleData { + String getValue() throws Err; + + void setValue(String v) throws Err; + } + + static Logic sampleLogic = + data -> { + String v = data.getValue(); + data.setValue(v); + v = data.getValue(); + data.setValue(v); + }; + static interface AllLogicData extends SampleData {} static interface FooDataAcc extends DataAcc, AllLogicData { @@ -238,437 +219,130 @@ default String getValue() throws Err { static interface BarDataAcc extends DataAcc, AllLogicData { @Override - default void setValue(String text) throws Err { + default void setValue(String v) throws Err { var conn = getDataConn("bar", BarDataConn.class); - conn.setText(text); + conn.setText(v); + assertThat(conn.text).isEqualTo("hello"); } } - /// - static class SampleDataHub extends DataHub implements FooDataAcc, BarDataAcc {} /// @Nested - class TestLogicArgument { + class RunTest { @BeforeEach void beforeEach() { - DataHubInnerTest.resetGlobalVariables(); + DataHubInnerTest.resetGlobals(); } @AfterEach void afterEach() { - DataHubInnerTest.resetGlobalVariables(); + DataHubInnerTest.resetGlobals(); } @Test - void test() { + void testMain() { var logger = new ArrayList(); - Sabi.uses("foo", new FooDataSrc(1, "hello", logger, false)); - Sabi.uses("bar", new BarDataSrc(2, logger, false)); + uses("foo", new FooDataSrc(1, logger, false)); - try (var ac = Sabi.setup()) { - suppressWarnings_unused(ac); - try (var hub = new SampleDataHub()) { - new SampleLogic().run(hub); - } catch (Exception e) { - fail(e); - } + try (var ac = setup()) { + testApp(logger); } 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 data = new SampleDataHub()) { - data.run(new SampleLogic()); - } catch (Exception e) { - fail(e); - } + assertThat(logger).hasSize(16); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("FooDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("FooDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("BarDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("BarDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("FooDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("FooDataConn#new 1"); + assertThat(iter.next()).isEqualTo("FooDataConn#getText 1"); + assertThat(iter.next()).isEqualTo("BarDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("BarDataConn#new 2"); + assertThat(iter.next()).isEqualTo("BarDataConn#setText 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#getText 1"); + assertThat(iter.next()).isEqualTo("BarDataConn#setText 2"); + assertThat(iter.next()).isEqualTo("BarDataConn#close 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#close 1"); + assertThat(iter.next()).isEqualTo("BarDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("FooDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + void testApp(List logger) throws Err { + try (var hub = new SampleDataHub()) { + hub.uses("bar", new BarDataSrc(2, logger, false)); + hub.run(sampleLogic); } 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 { + class TxnTest { @BeforeEach void beforeEach() { - DataHubInnerTest.resetGlobalVariables(); + DataHubInnerTest.resetGlobals(); } @AfterEach void afterEach() { - DataHubInnerTest.resetGlobalVariables(); + DataHubInnerTest.resetGlobals(); } @Test - void test() { + void testMain() { var logger = new ArrayList(); - try (var ac = Sabi.setup()) { - suppressWarnings_unused(ac); - try (var data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(1, "hello", logger, false)); - data.uses("bar", new BarDataSrc(2, logger, false)); + uses("foo", new FooDataSrc(1, logger, false)); - data.run(new SampleLogic()); - } catch (Exception e) { - fail(e); - } + try (var ac = setup()) { + testApp(logger); } 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 data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(1, "hello", logger, true)); - data.uses("bar", new BarDataSrc(2, logger, false)); - - data.run(new SampleLogic()); - } catch (Err 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); - } + assertThat(logger).hasSize(22); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("FooDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("FooDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("BarDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("BarDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("FooDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("FooDataConn#new 1"); + assertThat(iter.next()).isEqualTo("FooDataConn#getText 1"); + assertThat(iter.next()).isEqualTo("BarDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("BarDataConn#new 2"); + assertThat(iter.next()).isEqualTo("BarDataConn#setText 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#getText 1"); + assertThat(iter.next()).isEqualTo("BarDataConn#setText 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("BarDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("BarDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("BarDataConn#postCommit 2"); + assertThat(iter.next()).isEqualTo("BarDataConn#close 2"); + assertThat(iter.next()).isEqualTo("FooDataConn#close 1"); + assertThat(iter.next()).isEqualTo("BarDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("FooDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + void testApp(List logger) throws Err { + try (var hub = new SampleDataHub()) { + hub.uses("bar", new BarDataSrc(2, logger, false)); + hub.txn(sampleLogic); } 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 data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(2, "Hello", logger, false)); - - data.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 data = new SampleDataHub()) { - data.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 data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(1, "Hello", logger, false)); - data.uses("bar", new BarDataSrc(2, logger, false)); - - data.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 data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(1, "Hello", logger, true)); - data.uses("bar", new BarDataSrc(2, logger, false)); - - data.txn(new SampleLogic()); - } catch (Err 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 data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(1, "Hello", logger, false)); - data.uses("bar", new BarDataSrc(2, logger, false)); - - data.txn(new FailingLogic()); - } catch (Err 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 data = new SampleDataHub()) { - data.uses("foo", new FooDataSrc(2, "Hello", logger, false)); - - data.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 deleted file mode 100644 index 61d4a11..0000000 --- a/src/test/java/com/github/sttk/sabi/internal/DataConnListTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.github.sttk.sabi.internal; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.github.sttk.errs.Err; -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.DataConn; -import java.util.ArrayList; -import java.util.List; -import org.junit.jupiter.api.Test; - -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 Err {} - - public void preCommit(AsyncGroup ag) throws Err {} - - 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/DataConnManagerTest.java b/src/test/java/com/github/sttk/sabi/internal/DataConnManagerTest.java new file mode 100644 index 0000000..f39566d --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/DataConnManagerTest.java @@ -0,0 +1,1352 @@ +package com.github.sttk.sabi.internal; + +import static com.github.sttk.sabi.DataConn.FailToCommitDataConn; +import static com.github.sttk.sabi.DataConn.FailToPostCommitDataConn; +import static com.github.sttk.sabi.DataConn.FailToPreCommitDataConn; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import com.github.sttk.errs.Err; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.TxnFailureReport; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class DataConnManagerTest { + private DataConnManagerTest() {} + + static enum Fail { + Not, + Commit, + PreCommit, + PostCommit, + Rollback, + PreCommitBecomeCommitted, + } + + static class SyncDataConn implements DataConn { + int id; + boolean committed; + Fail fail; + List logger; + + SyncDataConn(int id, List logger, Fail fail) { + this.id = id; + this.committed = false; + this.fail = fail; + this.logger = logger; + } + + @Override + public void commit(AsyncGroup ag) throws Err { + if (this.fail == Fail.Commit) { + this.logger.add(String.format("SyncDataConn#commit %d failed", this.id)); + throw new Err("ZZZ"); + } + this.committed = true; + this.logger.add(String.format("SyncDataConn#commit %d", this.id)); + } + + @Override + public void preCommit(AsyncGroup ag) throws Err { + if (this.fail == Fail.PreCommit) { + this.logger.add(String.format("SyncDataConn#preCommit %d failed", this.id)); + throw new Err("zzz"); + } + this.logger.add(String.format("SyncDataConn#preCommit %d", this.id)); + if (this.fail == Fail.PreCommitBecomeCommitted) { + this.committed = true; + } + } + + @Override + public void postCommit(AsyncGroup ag) throws Err { + if (this.fail == Fail.PostCommit) { + this.logger.add(String.format("SyncDataConn#postCommit %d failed", this.id)); + throw new Err("!!!"); + } + this.logger.add(String.format("SyncDataConn#postCommit %d", this.id)); + } + + @Override + public boolean isCommitted() { + return this.committed; + } + + @Override + public void rollback(AsyncGroup ag) throws Err { + if (this.fail == Fail.Rollback) { + this.logger.add(String.format("SyncDataConn#rollback %d failed", this.id)); + throw new Err("???"); + } + this.logger.add(String.format("SyncDataConn#rollback %d", this.id)); + } + + @Override + public void onTxnFailure(AsyncGroup ag, List reports) { + this.logger.add(String.format("SyncDataConn#onTxnFailure %d", this.id)); + this.logger.add(String.format("TxnFailureReports=%s", reports.toString())); + } + + @Override + public void close() { + this.logger.add(String.format("SyncDataConn#close %d", this.id)); + } + } + + static class AsyncDataConn implements DataConn { + int id; + boolean committed; + Fail fail; + List logger; + + AsyncDataConn(int id, List logger, Fail fail) { + this.id = id; + this.committed = false; + this.fail = fail; + this.logger = logger; + } + + @Override + public void commit(AsyncGroup ag) throws Err { + ag.add( + () -> { + try { + Thread.sleep(200); + } catch (Exception e) { + } + if (this.fail == Fail.Commit) { + this.logger.add(String.format("AsyncDataConn#commit %d failed", this.id)); + throw new Err("YYY"); + } + this.committed = true; + this.logger.add(String.format("AsyncDataConn#commit %d", this.id)); + }); + } + + @Override + public void preCommit(AsyncGroup ag) throws Err { + ag.add( + () -> { + try { + Thread.sleep(200); + } catch (Exception e) { + } + if (this.fail == Fail.PreCommit) { + this.logger.add(String.format("AsyncDataConn#preCommit %d failed", this.id)); + throw new Err("yyy"); + } + this.logger.add(String.format("AsyncDataConn#preCommit %d", this.id)); + if (this.fail == Fail.PreCommitBecomeCommitted) { + this.committed = true; + } + }); + } + + @Override + public void postCommit(AsyncGroup ag) throws Err { + ag.add( + () -> { + try { + Thread.sleep(200); + } catch (Exception e) { + } + if (this.fail == Fail.PostCommit) { + this.logger.add(String.format("AsyncDataConn#postCommit %d failed", this.id)); + throw new Err("!!!"); + } + this.logger.add(String.format("AsyncDataConn#postCommit %d", this.id)); + }); + } + + @Override + public boolean isCommitted() { + return this.committed; + } + + @Override + public void rollback(AsyncGroup ag) throws Err { + ag.add( + () -> { + try { + Thread.sleep(200); + } catch (Exception e) { + } + if (this.fail == Fail.Rollback) { + this.logger.add(String.format("AsyncDataConn#rollback %d failed", this.id)); + throw new Err("???"); + } + this.logger.add(String.format("AsyncDataConn#rollback %d", this.id)); + }); + } + + @Override + public void onTxnFailure(AsyncGroup ag, List reports) { + ag.add( + () -> { + try { + Thread.sleep(200); + } catch (Exception e) { + } + this.logger.add(String.format("AsyncDataConn#onTxnFailure %d", this.id)); + this.logger.add(String.format("TxnFailureReports=%s", reports.toString())); + }); + } + + @Override + public void close() { + this.logger.add(String.format("AsyncDataConn#close %d", this.id)); + } + } + + /// + + @Test + void testNew() { + var manager = new DataConnManager(); + assertThat(manager.list).isEmpty(); + assertThat(manager.indexMap).isEmpty(); + } + + @Test + void testNewWithCommitOrder() { + var manager = new DataConnManager(List.of("bar", "baz", "foo")); + assertThat(manager.list).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isNull(); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.indexMap.get("foo")).isEqualTo(2); + assertThat(manager.indexMap.get("bar")).isEqualTo(0); + assertThat(manager.indexMap.get("baz")).isEqualTo(1); + } + + @Test + void testNewAndAdd() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + assertThat(manager.list).isEmpty(); + assertThat(manager.indexMap).isEmpty(); + + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + assertThat(manager.list).hasSize(1); + assertThat(manager.indexMap).hasSize(1); + assertThat(manager.indexMap.get("foo")).isEqualTo(0); + assertThat(manager.list.get(0).conn).isEqualTo(conn1); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + assertThat(manager.list).hasSize(2); + assertThat(manager.indexMap).hasSize(2); + assertThat(manager.indexMap.get("foo")).isEqualTo(0); + assertThat(manager.indexMap.get("bar")).isEqualTo(1); + assertThat(manager.list.get(0).conn).isEqualTo(conn1); + assertThat(manager.list.get(1).conn).isEqualTo(conn2); + } + + @Test + void testAndAddWhenOverlappingName() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + assertThat(manager.list).isEmpty(); + assertThat(manager.indexMap).isEmpty(); + + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + assertThat(manager.list).hasSize(1); + assertThat(manager.indexMap).hasSize(1); + assertThat(manager.indexMap.get("foo")).isEqualTo(0); + assertThat(manager.list.get(0).conn).isEqualTo(conn1); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn2)); + assertThat(manager.list).hasSize(1); + assertThat(manager.indexMap).hasSize(1); + assertThat(manager.indexMap.get("foo")).isEqualTo(0); + assertThat(manager.list.get(0).conn).isEqualTo(conn1); + } + + @Test + void testNewWithCommitOrderAndAdd() { + var logger = new ArrayList(); + + var manager = new DataConnManager(List.of("bar", "baz", "foo")); + assertThat(manager.list).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isNull(); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.indexMap.get("foo")).isEqualTo(2); + assertThat(manager.indexMap.get("bar")).isEqualTo(0); + assertThat(manager.indexMap.get("baz")).isEqualTo(1); + + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isEqualTo(conn1); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.list.get(0).conn).isEqualTo(conn2); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isEqualTo(conn1); + + var conn3 = new SyncDataConn(3, logger, Fail.Not); + manager.add(new DataConnContainer("qux", conn3)); + assertThat(manager.indexMap).hasSize(4); + assertThat(manager.list.get(0).conn).isEqualTo(conn2); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isEqualTo(conn1); + assertThat(manager.list.get(3).conn).isEqualTo(conn3); + } + + @Test + void testNewWithCommitOrderAndAddWhenOverlappingName() { + var logger = new ArrayList(); + + var manager = new DataConnManager(List.of("bar", "baz", "foo")); + assertThat(manager.list).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isNull(); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.indexMap.get("foo")).isEqualTo(2); + assertThat(manager.indexMap.get("bar")).isEqualTo(0); + assertThat(manager.indexMap.get("baz")).isEqualTo(1); + + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isEqualTo(conn1); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn2)); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isEqualTo(conn1); + + var conn3 = new SyncDataConn(3, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn3)); + assertThat(manager.indexMap).hasSize(3); + assertThat(manager.list.get(0).conn).isNull(); + assertThat(manager.list.get(1).conn).isNull(); + assertThat(manager.list.get(2).conn).isEqualTo(conn1); + } + + @Test + void testNewFailureReports() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + assertThat(reportBuilders).isEmpty(); + + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + + reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + assertThat(reportBuilders).hasSize(2); + + var reports = reportBuilders.stream().map(b -> b.build()).collect(Collectors.toList()); + assertThat(reports.get(0).dataConnName).isEqualTo("foo"); + assertThat(reports.get(0).dataConnType).isEqualTo(SyncDataConn.class.getName()); + assertThat(reports.get(1).dataConnName).isEqualTo("bar"); + assertThat(reports.get(1).dataConnType).isEqualTo(AsyncDataConn.class.getName()); + } + + @Test + void testCommitAndRollbackOk() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + manager.commit(reportBuilders); + manager.rollback(reportBuilders); + } catch (Err err) { + fail(err); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(12); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#postCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitWithOrderAndRollbackOk() { + var logger = new ArrayList(); + + var manager = new DataConnManager(List.of("bar", "baz", "foo")); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new SyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + + var conn3 = new SyncDataConn(3, logger, Fail.Not); + manager.add(new DataConnContainer("qux", conn3)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + manager.commit(reportBuilders); + manager.rollback(reportBuilders); + } catch (Err err) { + fail(err); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(18); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 3"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 3"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 3"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:qux dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:qux dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 3"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:qux dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 3"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 2"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailFirstSyncPreCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.PreCommit); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.PreCommit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("zzz"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(9); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String zzz, file = DataConnManagerTest.java, line = 57 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String zzz, file = DataConnManagerTest.java, line = 57 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailFirstAsyncPreCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new AsyncDataConn(1, logger, Fail.PreCommit); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new SyncDataConn(2, logger, Fail.PreCommit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(2); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("zzz"); + ee = rsn.errors().get(1); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("yyy"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(10); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 2 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 1 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String yyy, file = DataConnManagerTest.java, line = 140 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String zzz, file = DataConnManagerTest.java, line = 57 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String yyy, file = DataConnManagerTest.java, line = 140 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String zzz, file = DataConnManagerTest.java, line = 57 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailSecondPreCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.PreCommit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToPreCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("yyy"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(10); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String yyy, file = DataConnManagerTest.java, line = 140 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:LogicFailure Err:com.github.sttk.errs.Err { reason = java.lang.String yyy, file = DataConnManagerTest.java, line = 140 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailFirstSyncCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Commit); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Commit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("ZZZ"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(11); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ZZZ, file = DataConnManagerTest.java, line = 47 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ZZZ, file = DataConnManagerTest.java, line = 47 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailFirstAsyncCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new AsyncDataConn(1, logger, Fail.Commit); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new SyncDataConn(2, logger, Fail.Commit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(2); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("ZZZ"); + ee = rsn.errors().get(1); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("YYY"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(12); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 2 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 1 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ZZZ, file = DataConnManagerTest.java, line = 47 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ZZZ, file = DataConnManagerTest.java, line = 47 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailSecondCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Commit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("YYY"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(11); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailFirstSyncPostCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.PostCommit); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.PostCommit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToPostCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(2); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("!!!"); + ee = rsn.errors().get(1); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("!!!"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(12); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 1 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#postCommit 2 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 69 }} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 159 }} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 69 }} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 159 }} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailFirstAsyncPostCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new AsyncDataConn(2, logger, Fail.PostCommit); + manager.add(new DataConnContainer("bar", conn1)); + + var conn2 = new SyncDataConn(1, logger, Fail.PostCommit); + manager.add(new DataConnContainer("foo", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToPostCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(2); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("!!!"); + ee = rsn.errors().get(1); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("!!!"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(12); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 1 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#postCommit 2 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 159 }} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 69 }} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 159 }} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 69 }} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackButFailSecondPostCommit() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.PostCommit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToPostCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("!!!"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(12); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#postCommit 2 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 159 }} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:PostCommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String !!!, file = DataConnManagerTest.java, line = 159 }} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testOnlyRollbackAndFirstIsSync() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + manager.rollback(reportBuilders); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testOnlyRollbackAndFirstIsAsync() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new AsyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new SyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + manager.rollback(reportBuilders); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testOnlyRollbackAndSecondRollbackFailed() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new AsyncDataConn(1, logger, Fail.Not); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new SyncDataConn(2, logger, Fail.Rollback); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + manager.rollback(reportBuilders); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 2 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ???, file = DataConnManagerTest.java, line = 83 }}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ???, file = DataConnManagerTest.java, line = 83 }}}]"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testOnlyRollbackAndFirstRollbackFailed() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Rollback); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Not); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + manager.rollback(reportBuilders); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 1 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ???, file = DataConnManagerTest.java, line = 83 }}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ???, file = DataConnManagerTest.java, line = 83 }}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackAndFirstRollbackFailedThenSecondRollbackFailed() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Commit); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Rollback); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(0); + assertThat(ee.name).isEqualTo("foo"); + assertThat(ee.err.getReason()).isEqualTo("ZZZ"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(11); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2 failed"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ZZZ, file = DataConnManagerTest.java, line = 47 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ???, file = DataConnManagerTest.java, line = 180 }}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ZZZ, file = DataConnManagerTest.java, line = 47 }} rollback:{State:NoneByRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:com.github.sttk.errs.Err { reason = java.lang.String ???, file = DataConnManagerTest.java, line = 180 }}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackAndSecondCommitFailedThenFirstRollbackFailed() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.Rollback); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Commit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("YYY"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(11); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackAndPreCommitBecomeCommittedAndOk() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.PreCommitBecomeCommitted); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.PreCommitBecomeCommitted); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(10); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#postCommit 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCommitAndRollbackAndPreCommitBecomeCommittedButFailed() { + var logger = new ArrayList(); + + var manager = new DataConnManager(); + try { + var conn1 = new SyncDataConn(1, logger, Fail.PreCommitBecomeCommitted); + manager.add(new DataConnContainer("foo", conn1)); + + var conn2 = new AsyncDataConn(2, logger, Fail.Commit); + manager.add(new DataConnContainer("bar", conn2)); + + var reportBuilders = new ArrayList(); + manager.prepareTxnFailureReportBuilders(reportBuilders); + + try { + manager.commit(reportBuilders); + } catch (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("YYY"); + } + default -> fail(err); + } + manager.rollback(reportBuilders); + } catch (Exception e) { + fail(e); + } + } finally { + manager.close(); + } + + assertThat(logger).hasSize(10); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#commit 2 failed"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#onTxnFailure 1"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#onTxnFailure 2"); + assertThat(iter.next()) + .isEqualTo( + "TxnFailureReports=[{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$SyncDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}, {dataConnName:bar dataConnType:com.github.sttk.sabi.internal.DataConnManagerTest$AsyncDataConn cause:{State:CommitFailure Err:com.github.sttk.errs.Err { reason = java.lang.String YYY, file = DataConnManagerTest.java, line = 123 }} rollback:{State:NoneByRolledBack Err:null}}]"); + assertThat(iter.next()).isEqualTo("AsyncDataConn#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataConn#close 1"); + assertThat(iter.hasNext()).isFalse(); + } +} diff --git a/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java b/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java index 3227e6a..b5bd205 100644 --- a/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java +++ b/src/test/java/com/github/sttk/sabi/internal/DataHubInnerTest.java @@ -1,2080 +1,1596 @@ package com.github.sttk.sabi.internal; +import static com.github.sttk.sabi.DataConn.FailToCommitDataConn; +import static com.github.sttk.sabi.DataConn.FailToPostCommitDataConn; +import static com.github.sttk.sabi.DataConn.FailToPreCommitDataConn; +import static com.github.sttk.sabi.DataHub.CreatedDataConnIsNull; +import static com.github.sttk.sabi.DataHub.FailToCastDataConn; +import static com.github.sttk.sabi.DataHub.FailToCastDataHub; +import static com.github.sttk.sabi.DataHub.FailToCreateDataConn; +import static com.github.sttk.sabi.DataHub.FailToSetupGlobalDataSrcs; +import static com.github.sttk.sabi.DataHub.FailToSetupLocalDataSrcs; +import static com.github.sttk.sabi.DataHub.NoDataSrcToCreateDataConn; +import static com.github.sttk.sabi.Sabi.setup; +import static com.github.sttk.sabi.Sabi.uses; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.fail; import com.github.sttk.errs.Err; import com.github.sttk.sabi.AsyncGroup; import com.github.sttk.sabi.DataConn; import com.github.sttk.sabi.DataHub; import com.github.sttk.sabi.DataSrc; +import com.github.sttk.sabi.TxnFailureReport; import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; public class DataHubInnerTest { private DataHubInnerTest() {} - static final int FAIL__NOT = 0; - static final int FAIL__SETUP = 1; - static final int FAIL__CREATE_DATA_CONN = 2; - static final int FAIL__COMMIT = 3; - static final int FAIL__PRE_COMMIT = 4; - - final void suppressWarnings_unused(Object a) {} + enum Failure { + None, + PreCommit, + Commit, + PostCommit, + Rollback, + Setup, + CreateDataConn, + CreatedDataConnIsNull, + } - static class SyncDataSrc implements DataSrc { - private int id; - private int fail; - private List logger; + static class MyDataConn implements DataConn { + int id; + Failure failure; + boolean committed; + List logger; - SyncDataSrc(int id, int fail, List logger) { + MyDataConn(int id, Failure failure, List logger) { this.id = id; - this.fail = fail; + this.failure = failure; this.logger = logger; + this.committed = false; } @Override - public void setup(AsyncGroup ag) throws Err { - if (this.fail == FAIL__SETUP) { - this.logger.add(String.format("SyncDataSrc %d failed to setup", this.id)); - throw new Err("XXX"); - } - this.logger.add(String.format("SyncDataSrc %d setupped", this.id)); + public boolean isCommitted() { + return this.committed; } @Override - public void close() { - this.logger.add(String.format("SyncDataSrc %d closed", this.id)); + public void preCommit(AsyncGroup ag) throws Err { + if (this.failure == Failure.PreCommit) { + this.logger.add(String.format("MyDataConn#preCommit %d failed", this.id)); + throw new Err("pre commit error"); + } + this.logger.add(String.format("MyDataConn#preCommit %d", this.id)); } @Override - public DataConn createDataConn() throws Err { - if (this.fail == FAIL__CREATE_DATA_CONN) { - this.logger.add(String.format("SyncDataSrc %d failed to create a DataConn", this.id)); - throw new Err("xxx"); + public void commit(AsyncGroup ag) throws Err { + if (this.failure == Failure.Commit) { + this.logger.add(String.format("MyDataConn#commit %d failed", this.id)); + throw new Err("commit error"); } - this.logger.add(String.format("SyncDataSrc %d created DataConn", this.id)); - var conn = new SyncDataConn(this.id, this.fail, this.logger); - return conn; + this.logger.add(String.format("MyDataConn#commit %d", this.id)); } - } - - 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 postCommit(AsyncGroup ag) throws Err { + if (this.failure == Failure.PostCommit) { + this.logger.add(String.format("MyDataConn#postCommit %d failed", this.id)); + throw new Err("post commit error"); + } + this.logger.add(String.format("MyDataConn#postCommit %d", this.id)); } @Override - public void setup(AsyncGroup ag) throws Err { - 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 Err("YYY"); - } - this.logger.add(String.format("AsyncDataSrc %d setupped", this.id)); - }); + public void rollback(AsyncGroup ag) throws Err { + if (this.failure == Failure.Rollback) { + this.logger.add(String.format("MyDataConn#rollback %d failed", this.id)); + throw new Err("rollback error"); + } + this.logger.add(String.format("MyDataConn#rollback %d", this.id)); } @Override - public void close() { - this.logger.add(String.format("AsyncDataSrc %d closed", this.id)); + public void onTxnFailure(AsyncGroup ag, List repots) { + this.logger.add(String.format("MyDataConn#onTxnFailure %d", this.id)); } @Override - public DataConn createDataConn() throws Err { - if (this.fail == FAIL__CREATE_DATA_CONN) { - this.logger.add(String.format("AsyncDataSrc %d failed to create a DataConn", this.id)); - throw new Err("yyy"); - } - this.logger.add(String.format("AsyncDataSrc %d created DataConn", this.id)); - var conn = new AsyncDataConn(this.id, this.fail, this.logger); - return conn; + public void close() { + this.logger.add(String.format("MyDataConn#close %d", this.id)); } } - static class SyncDataConn implements DataConn { - private int id; - private int fail; - private boolean committed; - private List logger; + static class MyDataSrc implements DataSrc { + int id; + Failure failure; + List logger; - SyncDataConn(int id, int fail, List logger) { + MyDataSrc(int id, Failure failure, List logger) { this.id = id; - this.fail = fail; + this.failure = failure; this.logger = logger; } @Override - public void commit(AsyncGroup ag) throws Err { - if (this.fail == FAIL__COMMIT) { - this.logger.add(String.format("SyncDataConn %d failed to commit", this.id)); - throw new Err("ZZZ"); - } - this.committed = true; - this.logger.add(String.format("SyncDataConn %d committed", this.id)); - } - - @Override - public void preCommit(AsyncGroup ag) throws Err { - if (this.fail == FAIL__PRE_COMMIT) { - this.logger.add(String.format("SyncDataConn %d failed to pre commit", this.id)); - throw new Err("zzz"); + public void setup(AsyncGroup ag) throws Err { + if (this.failure == Failure.Setup) { + this.logger.add(String.format("MyDataSrc#setup %d failed", this.id)); + throw new Err("setup error"); } - this.logger.add(String.format("SyncDataConn %d pre committed", this.id)); + this.logger.add(String.format("MyDataSrc#setup %d", this.id)); } @Override - public void postCommit(AsyncGroup ag) { - this.logger.add(String.format("SyncDataConn %d post committed", this.id)); + public void close() { + this.logger.add(String.format("MyDataSrc#close %d", this.id)); } @Override - public boolean shouldForceBack() { - return this.committed; + public DataConn createDataConn() throws Err { + if (this.failure == Failure.CreateDataConn) { + this.logger.add(String.format("MyDataSrc#createDataConn %d failed", this.id)); + throw new Err("eeee"); + } + if (this.failure == Failure.CreatedDataConnIsNull) { + this.logger.add(String.format("MyDataSrc#createDataConn %d is null", this.id)); + return null; + } + this.logger.add(String.format("MyDataSrc#createDataConn %d", this.id)); + return new MyDataConn(this.id, this.failure, this.logger); } + } - @Override - public void rollback(AsyncGroup ag) { - this.logger.add(String.format("SyncDataConn %d rollbacked", this.id)); - } + static class BadDataConn implements DataConn { + BadDataConn(int id, Failure failure, List logger) {} @Override - public void forceBack(AsyncGroup ag) { - this.logger.add(String.format("SyncDataConn %d forced back", this.id)); + public boolean isCommitted() { + return true; } @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; - } + public void preCommit(AsyncGroup ag) throws Err {} @Override - public void commit(AsyncGroup ag) throws Err { - if (this.fail == FAIL__COMMIT) { - this.logger.add(String.format("AsyncDataConn %d failed to commit", this.id)); - throw new Err("VVV"); - } - this.committed = true; - this.logger.add(String.format("AsyncDataConn %d committed", this.id)); - } + public void commit(AsyncGroup ag) throws Err {} @Override - public void preCommit(AsyncGroup ag) throws Err { - if (this.fail == FAIL__PRE_COMMIT) { - this.logger.add(String.format("AsyncDataConn %d failed to pre commit", this.id)); - throw new Err("vvv"); - } - this.logger.add(String.format("AsyncDataConn %d pre committed", this.id)); - } + public void postCommit(AsyncGroup ag) throws Err {} @Override - public void postCommit(AsyncGroup ag) { - this.logger.add(String.format("AsyncDataConn %d post committed", this.id)); - } + public void rollback(AsyncGroup ag) throws Err {} @Override - public boolean shouldForceBack() { - return this.committed; - } + public void onTxnFailure(AsyncGroup ag, List repots) {} @Override - public void rollback(AsyncGroup ag) { - this.logger.add(String.format("AsyncDataConn %d rollbacked", this.id)); - } + public void close() {} + } - @Override - public void forceBack(AsyncGroup ag) { - this.logger.add(String.format("AsyncDataConn %d forced back", this.id)); - } + static interface FailToCastData {} - @Override - public void close() { - this.logger.add(String.format("AsyncDataConn %d closed", this.id)); + int countDs(List list) { + int n = 0; + for (var cont : list) { + if (cont.ds != null) { + n++; + } } + return n; } - static void resetGlobalVariables() { + static void resetGlobals() { + DataHubInner.GLOBAL_DATA_SRC_MANAGER.close(); DataHubInner.GLOBAL_DATA_SRCS_FIXED.set(false); - DataHubInner.GLOBAL_DATA_SRC_LIST.closeDataSrcs(); } - @Nested - class TestOfGlobalFunctions { - @BeforeEach - void beforeEach() { - resetGlobalVariables(); + /// + + @Test + void testNewDataHubImpl() { + var hub = new DataHubInner(); + try { + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); + } finally { + hub.closeLocals(); } + } - @AfterEach - void afterEach() { - resetGlobalVariables(); + @Test + void testNewDataHubWithCommitOrder() { + var hub = new DataHubInner(List.of("bar", "qux", "foo")); + try { + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).hasSize(3); + assertThat(hub.dataConnManager.indexMap).hasSize(3); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); + } finally { + hub.closeLocals(); } + } - @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(); + @Test + void testUsesAndOk() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + hub.useLocal("bar", new MyDataSrc(2, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(2); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - assertThat(logger) - .containsExactly( - "SyncDataSrc 2 setupped", - "AsyncDataSrc 1 setupped", - "SyncDataSrc 2 closed", - "AsyncDataSrc 1 closed"); + hub.begin(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); } + } - @Test - void fail_to_setup() { - var logger = new ArrayList(); + @Test + void testUsesButAlreadyFixed() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(1); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + hub.begin(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(1); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + hub.useLocal("bar", new MyDataSrc(2, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(1); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); + } + } - DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__SETUP, logger)); - DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__SETUP, logger)); + @Test + void testDisuseAndOk() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + hub.useLocal("bar", new MyDataSrc(2, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(2); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - 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(); + hub.disuseLocal("foo"); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(1); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - try (var ac = DataHubInner.setupGlobals()) { - suppressWarnings_unused(ac); - fail(); - } catch (Err 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); - } + hub.disuseLocal("bar"); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - assertThat(logger) - .containsExactly("SyncDataSrc 2 failed to setup", "AsyncDataSrc 1 failed to setup"); + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); } + } - @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(); + @Test + void testDisuseAndFix() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + hub.useLocal("bar", new MyDataSrc(2, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(2); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + hub.disuseLocal("foo"); - var ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead; - assertThat(ptr).isNotNull(); - assertThat(ptr.name).isEqualTo("foo"); - ptr = ptr.next; - assertThat(ptr).isNull(); + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(1); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + hub.disuseLocal("bar"); - try (var ac = DataHubInner.setupGlobals()) { - suppressWarnings_unused(ac); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; - assertThat(ptr).isNotNull(); - assertThat(ptr.name).isEqualTo("foo"); - ptr = ptr.next; - assertThat(ptr).isNull(); + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + hub.useLocal("bar", new MyDataSrc(2, Failure.None, logger)); + + hub.begin(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + hub.disuseLocal("foo"); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + hub.disuseLocal("bar"); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + hub.disuseLocal("foo"); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(1); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(1); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; - assertThat(ptr).isNotNull(); - assertThat(ptr.name).isEqualTo("foo"); - ptr = ptr.next; - assertThat(ptr).isNull(); + hub.disuseLocal("bar"); - } catch (Exception e) { - fail(e); - } - - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - assertThat(logger).containsExactly("AsyncDataSrc 1 setupped", "AsyncDataSrc 1 closed"); + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); } + } - @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(); + @Test + void testBeginIfEmpty() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.begin(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead).isNull(); + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); + } + } - try (var ac = DataHubInner.setupGlobals()) { - suppressWarnings_unused(ac); - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + @Test + void testBeginAndOk() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + hub.useLocal("bar", new MyDataSrc(2, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(2); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; - assertThat(ptr).isNotNull(); - assertThat(ptr.name).isEqualTo("foo"); - ptr = ptr.next; - assertThat(ptr).isNull(); + hub.begin(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isTrue(); + + hub.end(); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(0); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(2); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).hasSize(2); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); + } - assertThat(DataHubInner.GLOBAL_DATA_SRC_LIST.notSetupHead).isNull(); + assertThat(logger).hasSize(4); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } - ptr = DataHubInner.GLOBAL_DATA_SRC_LIST.didSetupHead; - assertThat(ptr).isNotNull(); - assertThat(ptr.name).isEqualTo("foo"); - ptr = ptr.next; - assertThat(ptr).isNull(); + @Test + void testBeginButFailed() { + var logger = new ArrayList(); + + var hub = new DataHubInner(); + try { + hub.useLocal("foo", new MyDataSrc(1, Failure.None, logger)); + hub.useLocal("bar", new MyDataSrc(2, Failure.Setup, logger)); + hub.useLocal("baz", new MyDataSrc(3, Failure.None, logger)); + + assertThat(countDs(hub.localDataSrcManager.listUnready)).isEqualTo(3); + assertThat(countDs(hub.localDataSrcManager.listReady)).isEqualTo(0); + assertThat(hub.localDataSrcManager.local).isTrue(); + assertThat(hub.dataSrcMap).isEmpty(); + assertThat(hub.dataConnManager.list).isEmpty(); + assertThat(hub.dataConnManager.indexMap).isEmpty(); + assertThat(hub.dataConnMap).isEmpty(); + assertThat(hub.fixed).isFalse(); - } catch (Exception e) { - fail(e); + try { + hub.begin(); + fail(); + } catch (Err err) { + switch (err.getReason()) { + case FailToSetupLocalDataSrcs rsn -> { + assertThat(rsn.errors()).hasSize(1); + var ee = rsn.errors().get(0); + assertThat(ee.index).isEqualTo(1); + assertThat(ee.name).isEqualTo("bar"); + assertThat(ee.err.getReason()).isEqualTo("setup error"); + } + default -> fail(err); + } + } finally { + hub.end(); } - - 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"); + } catch (Exception e) { + fail(e); + } finally { + hub.closeLocals(); } + + assertThat(logger).hasSize(3); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2 failed"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Nested - class TestOfDataHubLocal { - @BeforeEach - void beforeEach() { - resetGlobalVariables(); - } - - @AfterEach - void afterEach() { - resetGlobalVariables(); - } - + class RunTest { @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(); + void testRunAndOk() { + var logger = new ArrayList(); - hub.close(); - } + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); - @Test - void new_and_close_with_global_data_srcs() { - var logger = new ArrayList(); + hub.run( + data -> { + logger.add("execute logic"); + }); - 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"); + assertThat(logger).hasSize(5); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void uses_and_disuses() { + void testRunButFailedToRunLogic() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); + + hub.run( + data -> { + logger.add("execute logic but fail"); + }); + } catch (Err err) { + switch (err.getReason()) { + case String s -> { + assertThat(s).isEqualTo("logic error but fail"); + } + default -> fail(err); + } } 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"); + assertThat(logger).hasSize(5); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic but fail"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void cannot_add_and_remove_data_src_between_begin_and_end() { + void testRunButFailToCastToSpecifiedDataHub() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); - 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(); + hub.run((FailToCastData data) -> {}); + } catch (Err err) { + switch (err.getReason()) { + case FailToCastDataHub r -> { + assertThat(r.fromType()).isEqualTo(DataHub.class.getName()); + } + default -> fail(err); + } } catch (Exception e) { fail(e); } - assertThat(logger).containsExactly("SyncDataSrc 1 setupped", "SyncDataSrc 1 closed"); + assertThat(logger).hasSize(4); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void begin_and_end() { + void testRunButFailToSetup() { 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); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.Setup, logger)); + + hub.run( + data -> { + logger.add("execute logic"); + }); + } catch (Err err) { + switch (err.getReason()) { + case FailToSetupLocalDataSrcs r -> { + assertThat(r.errors()).hasSize(1); + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("foo"); + assertThat(r.errors().get(0).err.toString()) + .isEqualTo( + "com.github.sttk.errs.Err { reason = java.lang.String setup error, file = DataHubInnerTest.java, line = 123 }"); + } + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(1); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1 failed"); + assertThat(iter.hasNext()).isFalse(); } + } + @Nested + class TxnTest { @Test - void begin_and_end_but_fail_sync() { + void testTxnAndNoDataAccessAndOk() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); - hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); - hub.uses("qux", new SyncDataSrc(4, FAIL__SETUP, logger)); - - try { - hub.begin(); - } catch (Err 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(); + hub.txn( + data -> { + logger.add("execute logic"); + }); } 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"); + assertThat(logger).hasSize(5); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void begin_and_end_but_fail_async() { + void testTxnAndHasDataAccessAndOk() { 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 (Err 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); - } + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); - 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.txn( + (DataHub data) -> { + logger.add("execute logic"); - hub.end(); + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - 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(); + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); + }); } 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"); + assertThat(logger).hasSize(15); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void commit() { + void testTxnButFailedToRunLogic() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); - conn2 = hub.getDataConn("bar", SyncDataConn.class); - assertThat(conn2).isNotNull(); + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - conn3 = hub.getDataConn("baz", AsyncDataConn.class); - assertThat(conn3).isNotNull(); + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - conn4 = hub.getDataConn("qux", SyncDataConn.class); - assertThat(conn4).isNotNull(); + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); - hub.commit(); - } catch (Exception e) { - fail(e); + throw new Err("logic error"); + }); + } catch (Err err) { + switch (err.getReason()) { + case String s -> { + assertThat(s).isEqualTo("logic error"); + } + default -> fail(err); } - - 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 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"); + assertThat(logger).hasSize(13); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void fail_to_cast_new_data_conn() { + void testTxnButFailedToPreCommit() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.PreCommit, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.PreCommit, logger)); - hub.uses("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - try { - hub.begin(); - } catch (Exception e) { - fail(e); - } + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - try { - hub.getDataConn("foo", SyncDataConn.class); - fail(); - } catch (Err 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); - } + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); + }); - try { - hub.getDataConn("bar", AsyncDataConn.class); - fail(); - } catch (Err 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 (Err err) { + switch (err.getReason()) { + case FailToPreCommitDataConn r -> { + assertThat(r.errors()).hasSize(1); + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("foo"); + assertThat(r.errors().get(0).err.getReason()).isEqualTo("pre commit error"); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(14); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 1 failed"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void fail_to_cast_reused_data_conn() { + void testTxnButFailedToCommit() { 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 (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.Commit, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.Commit, logger)); - try { - hub.begin(); - } catch (Exception e) { - fail(e); - } + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - var conn1 = hub.getDataConn("foo", AsyncDataConn.class); - assertThat(conn1).isInstanceOf(AsyncDataConn.class); + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - var conn2 = hub.getDataConn("bar", SyncDataConn.class); - assertThat(conn2).isInstanceOf(SyncDataConn.class); + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); + }); - try { - hub.getDataConn("foo", SyncDataConn.class); - fail(); - } catch (Err 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 (Err 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 (Err err) { + switch (err.getReason()) { + case FailToCommitDataConn r -> { + assertThat(r.errors()).hasSize(1); + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("foo"); + assertThat(r.errors().get(0).err.getReason()).isEqualTo("commit error"); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(16); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 1 failed"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void fail_to_create_data_conn() { + void testTxnButFailedToPostCommit() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.PostCommit, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.PostCommit, logger)); - hub.uses("bar", new SyncDataSrc(2, FAIL__CREATE_DATA_CONN, logger)); + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - try { - hub.begin(); - } catch (Exception e) { - fail(e); - } + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - var conn1 = hub.getDataConn("foo", AsyncDataConn.class); - assertThat(conn1).isInstanceOf(AsyncDataConn.class); + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); + }); - try { - hub.getDataConn("bar", AsyncDataConn.class); - fail(); - } catch (Err 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 (Err err) { + switch (err.getReason()) { + case FailToPostCommitDataConn r -> { + assertThat(r.errors()).hasSize(2); + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("foo"); + assertThat(r.errors().get(0).err.getReason()).isEqualTo("post commit error"); + assertThat(r.errors().get(1).index).isEqualTo(1); + assertThat(r.errors().get(1).name).isEqualTo("bar"); + assertThat(r.errors().get(1).err.getReason()).isEqualTo("post commit error"); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(17); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 1 failed"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 2 failed"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void fail_to_create_data_conn_because_of_no_data_src() { + void testButFailedToRollback() { var logger = new ArrayList(); - DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.Rollback, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.Rollback, logger)); - try (var ac = DataHubInner.setupGlobals()) { - suppressWarnings_unused(ac); - var hub = new DataHubInner(); + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - hub.uses("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - try { - hub.begin(); - } catch (Exception e) { - fail(e); - } + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); - try { - hub.getDataConn("baz", SyncDataConn.class); - fail(); - } catch (Err 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); - } + throw new Err("logic error"); + }); - try { - hub.getDataConn("qux", AsyncDataConn.class); - fail(); - } catch (Err 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 (Err err) { + switch (err.getReason()) { + case String s -> { + assertThat(s).isEqualTo("logic error"); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - hub.close(); } catch (Exception e) { fail(e); } - assertThat(logger) - .containsExactly( - "AsyncDataSrc 1 setupped", - "SyncDataSrc 2 setupped", - "SyncDataSrc 2 closed", - "AsyncDataSrc 1 closed"); + assertThat(logger).hasSize(13); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 1 failed"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 2 failed"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void commit_when_no_data_conn() { + void testWithCommitOrder() { var logger = new ArrayList(); - DataHubInner.usesGlobal("foo", new AsyncDataSrc(1, FAIL__NOT, logger)); - DataHubInner.usesGlobal("bar", new SyncDataSrc(2, FAIL__NOT, logger)); + try (var hub = new DataHub("bar", "foo")) { + hub.uses("foo", new MyDataSrc(1, Failure.Rollback, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.Rollback, logger)); - try (var ac = DataHubInner.setupGlobals()) { - suppressWarnings_unused(ac); - var hub = new DataHubInner(); + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); - hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - try { - hub.begin(); - hub.commit(); - hub.end(); - } catch (Exception e) { - fail(e); - } + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", MyDataConn.class); + }); - 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"); + assertThat(logger).hasSize(15); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void commit_but_fail_global_sync() { + void testTxnButFailToCastToSpecifiedDataHub() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); - var conn3 = hub.getDataConn("baz", AsyncDataConn.class); - assertThat(conn3).isNotNull(); - - var conn4 = hub.getDataConn("qux", SyncDataConn.class); - assertThat(conn4).isNotNull(); - - try { - hub.commit(); - } catch (Err 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(); + hub.txn((FailToCastData data) -> {}); + } catch (Err err) { + switch (err.getReason()) { + case FailToCastDataHub r -> { + assertThat(r.fromType()).isEqualTo(DataHub.class.getName()); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(4); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void commit_but_fail_global_async() { + void testTxnButFailToSetup() { 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 (Err 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.Setup, logger)); + + hub.txn( + (FailToCastData data) -> { + logger.add("execute logic"); + }); + } catch (Err err) { + switch (err.getReason()) { + case FailToSetupLocalDataSrcs r -> { + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("foo"); + assertThat(r.errors().get(0).err.getReason()).isEqualTo("setup error"); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(1); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1 failed"); + assertThat(iter.hasNext()).isFalse(); } @Test - void commit_but_fail_local_sync() { + void testGetDataConnCached() { 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); - hub.uses("baz", new AsyncDataSrc(3, FAIL__NOT, logger)); - hub.uses("qux", new SyncDataSrc(4, FAIL__COMMIT, logger)); + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); - hub.begin(); + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); - var conn1 = hub.getDataConn("foo", AsyncDataConn.class); - assertThat(conn1).isNotNull(); - - var conn2 = hub.getDataConn("bar", SyncDataConn.class); - assertThat(conn2).isNotNull(); + @SuppressWarnings("unused") + var dc2 = data.getDataConn("foo", MyDataConn.class); + }); + } catch (Exception e) { + fail(e); + } - var conn3 = hub.getDataConn("baz", AsyncDataConn.class); - assertThat(conn3).isNotNull(); + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#preCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#commit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#postCommit 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } - var conn4 = hub.getDataConn("qux", SyncDataConn.class); - assertThat(conn4).isNotNull(); + @Test + void testGetDataConnAndNoDataSrcToCreateDataConn() { + var logger = new ArrayList(); - try { - hub.commit(); - } catch (Err 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(); + try (var hub = new DataHub()) { + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); + + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); + }); + } catch (Err err) { + switch (err.getReason()) { + case NoDataSrcToCreateDataConn r -> { + assertThat(r.name()).isEqualTo("foo"); + assertThat(r.dataConnType()).isEqualTo(MyDataConn.class.getName()); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(1); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.hasNext()).isFalse(); } @Test - void commit_but_fail_local_async() { + void testGetDataConnAndCreatedDataConnIsNull() { 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 (Err 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.CreatedDataConnIsNull, logger)); + + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); + + @SuppressWarnings("unused") + var dc = data.getDataConn("foo", MyDataConn.class); + }); + } catch (Err err) { + switch (err.getReason()) { + case CreatedDataConnIsNull r -> { + assertThat(r.name()).isEqualTo("foo"); + assertThat(r.dataConnType()).isEqualTo(MyDataConn.class.getName()); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(4); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1 is null"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void pre_commit_but_fail_global_sync() { + void testGetDataConnAndFailedToCreateDataConn() { 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 (Err 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.CreateDataConn, logger)); + + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); + + @SuppressWarnings("unused") + var dc = data.getDataConn("foo", MyDataConn.class); + }); + } catch (Err err) { + switch (err.getReason()) { + case FailToCreateDataConn r -> { + assertThat(r.name()).isEqualTo("foo"); + assertThat(r.dataConnType()).isEqualTo(MyDataConn.class.getName()); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(4); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1 failed"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void pre_commit_but_fail_global_async() { + void testGetDataConnAndFailedToCastDataConn() { 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 (Err 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(); + try (var hub = new DataHub()) { + hub.uses("foo", new MyDataSrc(1, Failure.None, logger)); + hub.uses("bar", new MyDataSrc(2, Failure.None, logger)); + + hub.txn( + (DataHub data) -> { + logger.add("execute logic"); + + @SuppressWarnings("unused") + var dc1 = data.getDataConn("foo", MyDataConn.class); + + @SuppressWarnings("unused") + var dc2 = data.getDataConn("bar", BadDataConn.class); + }); + } catch (Err err) { + switch (err.getReason()) { + case FailToCastDataConn r -> { + assertThat(r.name()).isEqualTo("bar"); + assertThat(r.fromDataConnType()).isEqualTo(MyDataConn.class.getName()); + assertThat(r.toDataConnType()).isEqualTo(BadDataConn.class.getName()); } - } catch (Exception e) { - fail(e); + default -> fail(err); } - - 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"); + assertThat(logger).hasSize(13); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("execute logic"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#createDataConn 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#rollback 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 1"); + assertThat(iter.next()).isEqualTo("MyDataConn#onTxnFailure 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 2"); + assertThat(iter.next()).isEqualTo("MyDataConn#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } + } + + @Nested + class TestGlobals { @Test - void pre_commit_but_fail_local_sync() { + void testUsesAndSetupAndOk() { 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 (Err 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); + try { + resetGlobals(); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + + uses("foo", new MyDataSrc(1, Failure.None, logger)); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(1); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(0); + + try (var ac = setup()) { + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(0); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(1); } - - hub.end(); - - hub.close(); } catch (Exception e) { fail(e); + } finally { + resetGlobals(); } - 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"); + assertThat(logger).hasSize(2); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); } @Test - void pre_commit_but_fail_local_async() { + void testUsesAndSetupButFail() { var logger = new ArrayList(); + try { + resetGlobals(); - 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(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); - hub.uses("baz", new AsyncDataSrc(3, FAIL__PRE_COMMIT, logger)); - hub.uses("qux", new SyncDataSrc(4, FAIL__NOT, logger)); + uses("foo", new MyDataSrc(1, Failure.Setup, logger)); - hub.begin(); - - var conn1 = hub.getDataConn("foo", AsyncDataConn.class); - assertThat(conn1).isNotNull(); - - var conn2 = hub.getDataConn("bar", SyncDataConn.class); - assertThat(conn2).isNotNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(1); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(0); - var conn3 = hub.getDataConn("baz", AsyncDataConn.class); - assertThat(conn3).isNotNull(); - - var conn4 = hub.getDataConn("qux", SyncDataConn.class); - assertThat(conn4).isNotNull(); - - try { - hub.commit(); - } catch (Err e) { - switch (e.getReason()) { - case DataHub.FailToPreCommitDataConn rsn -> { - assertThat(rsn.errors()).hasSize(1); - var e2 = rsn.errors().get("baz"); - assertThat(e2.getReason()).isEqualTo("vvv"); + try (var ac = setup()) { + fail(); + } catch (Err err) { + switch (err.getReason()) { + case FailToSetupGlobalDataSrcs r -> { + assertThat(r.errors()).hasSize(1); + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("foo"); + assertThat(r.errors().get(0).err.getReason()).isEqualTo("setup error"); } - default -> fail(); + default -> fail(err); } - } catch (Exception e) { - fail(e); + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(0); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(0); } - - hub.end(); - - hub.close(); } catch (Exception e) { fail(e); + } finally { + resetGlobals(); } - 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"); + assertThat(logger).hasSize(1); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1 failed"); + assertThat(iter.hasNext()).isFalse(); } @Test - void rollback() { + void testUsesAndSetupButAlreadyFixedBefore() { 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(); + try { + resetGlobals(); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + + try (var ac = setup()) { + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + + uses("foo", new MyDataSrc(1, Failure.Setup, logger)); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + } } catch (Exception e) { fail(e); + } finally { + resetGlobals(); } - 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"); + assertThat(logger).hasSize(0); } @Test - void force_back() { + void testUsesAndSetupWithOrderAndOk() { var logger = new ArrayList(); + try { + resetGlobals(); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + + uses("foo", new MyDataSrc(1, Failure.None, logger)); + uses("bar", new MyDataSrc(2, Failure.None, logger)); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(2); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(0); + + try (var ac = setup("bar", "foo")) { + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(0); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(2); + } + } catch (Exception e) { + fail(e); + } finally { + resetGlobals(); + } - 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)); + assertThat(logger).hasSize(4); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 1"); + assertThat(iter.next()).isEqualTo("MyDataSrc#close 2"); + assertThat(iter.hasNext()).isFalse(); + } - hub.begin(); + @Test + void testUsesAndSetupWithOrderButFail() { + var logger = new ArrayList(); + try { + resetGlobals(); - var conn1 = hub.getDataConn("foo", AsyncDataConn.class); - assertThat(conn1).isNotNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); - var conn2 = hub.getDataConn("bar", SyncDataConn.class); - assertThat(conn2).isNotNull(); + uses("foo", new MyDataSrc(1, Failure.Setup, logger)); + uses("bar", new MyDataSrc(2, Failure.Setup, logger)); - var conn3 = hub.getDataConn("baz", AsyncDataConn.class); - assertThat(conn3).isNotNull(); + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(2); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(0); - var conn4 = hub.getDataConn("qux", SyncDataConn.class); - assertThat(conn4).isNotNull(); + try (var ac = setup("bar", "foo")) { + fail(); + } catch (Err err) { + switch (err.getReason()) { + case FailToSetupGlobalDataSrcs r -> { + assertThat(r.errors()).hasSize(1); + assertThat(r.errors().get(0).index).isEqualTo(0); + assertThat(r.errors().get(0).name).isEqualTo("bar"); + assertThat(r.errors().get(0).err.getReason()).isEqualTo("setup error"); + } + default -> fail(err); + } + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).hasSize(0); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).hasSize(0); + } + } catch (Exception e) { + fail(e); + } finally { + resetGlobals(); + } - hub.commit(); - hub.rollback(); - hub.end(); + assertThat(logger).hasSize(1); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("MyDataSrc#setup 2 failed"); + assertThat(iter.hasNext()).isFalse(); + } - hub.close(); + @Test + void testUsesAndSetupWithOrderButAlreadyFixedBefore() { + var logger = new ArrayList(); + try { + resetGlobals(); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + + try (var ac = setup("bar", "foo")) { + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + + uses("foo", new MyDataSrc(1, Failure.Setup, logger)); + + assertThat(DataHubInner.GLOBAL_DATA_SRCS_FIXED.get()).isTrue(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.local).isFalse(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listUnready).isEmpty(); + assertThat(DataHubInner.GLOBAL_DATA_SRC_MANAGER.listReady).isEmpty(); + } } catch (Exception e) { fail(e); + } finally { + resetGlobals(); } - - 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 post committed", - "SyncDataConn 2 post committed", - "AsyncDataConn 3 post committed", - "SyncDataConn 4 post 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"); } } } diff --git a/src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java b/src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java deleted file mode 100644 index da381b7..0000000 --- a/src/test/java/com/github/sttk/sabi/internal/DataSrcListTest.java +++ /dev/null @@ -1,1000 +0,0 @@ -package com.github.sttk.sabi.internal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - -import com.github.sttk.errs.Err; -import com.github.sttk.sabi.AsyncGroup; -import com.github.sttk.sabi.DataConn; -import com.github.sttk.sabi.DataSrc; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import org.junit.jupiter.api.Test; - -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 Err { - if (this.willFail) { - logger.add(String.format("SyncDataSrc %d failed to setup", this.id)); - throw new Err("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 Err { - 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 Err { - 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 Err("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 Err { - 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 Err {} - - @Override - public void preCommit(AsyncGroup ag) throws Err {} - - @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 Err {} - - @Override - public void preCommit(AsyncGroup ag) throws Err {} - - @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 (Err e) { - fail(e); - } - - ptr = ptr.next; - try { - var conn = ptr.ds.createDataConn(); - assertThat(conn).isNotNull(); - } catch (Err 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 errMap = dsList.setupDataSrcs(); - assertThat(errMap).hasSize(1); - - var err = errMap.get("foo"); - assertThat(err.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 errMap = dsList.setupDataSrcs(); - assertThat(errMap).hasSize(0); - - dsList.closeDataSrcs(); - - assertThat(dsList.notSetupHead).isNull(); - assertThat(dsList.didSetupHead).isNull(); - } -} diff --git a/src/test/java/com/github/sttk/sabi/internal/DataSrcManagerTest.java b/src/test/java/com/github/sttk/sabi/internal/DataSrcManagerTest.java new file mode 100644 index 0000000..7e24631 --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/DataSrcManagerTest.java @@ -0,0 +1,660 @@ +package com.github.sttk.sabi.internal; + +import static com.github.sttk.sabi.internal.DataConnManagerTest.AsyncDataConn; +import static com.github.sttk.sabi.internal.DataConnManagerTest.SyncDataConn; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.sttk.errs.Err; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.DataSrc; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class DataSrcManagerTest { + private DataSrcManagerTest() {} + + static enum Fail { + Not, + Setup, + CreateDataConn, + } + + static class SyncDataSrc implements DataSrc { + int id; + List logger; + Fail fail; + + SyncDataSrc(int id, List logger, Fail fail) { + logger.add(String.format("SyncDataSrc#new %d", id)); + this.id = id; + this.logger = logger; + this.fail = fail; + } + + @Override + public void setup(AsyncGroup ag) throws Err { + if (this.fail == Fail.Setup) { + this.logger.add(String.format("SyncDataSrc#setup %d failed", this.id)); + throw new Err("XXX"); + } + this.logger.add(String.format("SyncDataSrc#setup %d", this.id)); + } + + @Override + public void close() { + this.logger.add(String.format("SyncDataSrc#close %d", this.id)); + } + + @Override + public DataConn createDataConn() throws Err { + if (this.fail == Fail.CreateDataConn) { + this.logger.add(String.format("SyncDataSrc#createDataConn %d failed", this.id)); + throw new Err("XXX"); + } + this.logger.add(String.format("SyncDataSrc#createDataConn %d", this.id)); + return new SyncDataConn(this.id, this.logger, DataConnManagerTest.Fail.Not); + } + } + + static class AsyncDataSrc implements DataSrc { + int id; + List logger; + Fail fail; + + AsyncDataSrc(int id, List logger, Fail fail) { + logger.add(String.format("AsyncDataSrc#new %d", id)); + this.id = id; + this.logger = logger; + this.fail = fail; + } + + @Override + public void setup(AsyncGroup ag) throws Err { + ag.add( + () -> { + if (this.fail == Fail.Setup) { + this.logger.add(String.format("AsyncDataSrc#setup %d failed", this.id)); + throw new Err("XXX"); + } + this.logger.add(String.format("AsyncDataSrc#setup %d", this.id)); + }); + } + + @Override + public void close() { + this.logger.add(String.format("AsyncDataSrc#close %d", this.id)); + } + + @Override + public DataConn createDataConn() throws Err { + if (this.fail == Fail.CreateDataConn) { + this.logger.add(String.format("AsyncDataSrc#createDataConn %d failed", this.id)); + throw new Err("XXX"); + } + this.logger.add(String.format("AsyncDataSrc#createDataConn %d", this.id)); + return new AsyncDataConn(this.id, this.logger, DataConnManagerTest.Fail.Not); + } + } + + /// + + @Test + void testNew() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).isEmpty(); + assertThat(manager.listReady).isEmpty(); + + manager = new DataSrcManager(false); + assertThat(manager.local).isFalse(); + assertThat(manager.listUnready).isEmpty(); + assertThat(manager.listReady).isEmpty(); + } + + @Test + void testAdd() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(1); + assertThat(manager.listReady).hasSize(0); + + assertThat(manager.listUnready.get(0).name).isEqualTo("foo"); + + var ds2 = new AsyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(2); + assertThat(manager.listReady).hasSize(0); + + assertThat(manager.listUnready.get(0).name).isEqualTo("foo"); + assertThat(manager.listUnready.get(1).name).isEqualTo("bar"); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(2); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#new 2"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testRemove() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new AsyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + var errors = manager.setup(); + assertThat(errors).hasSize(0); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + var ds4 = new AsyncDataSrc(4, logger, Fail.Not); + manager.add("qux", ds4); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(2); + assertThat(manager.listReady).hasSize(2); + + manager.remove("baz"); + manager.remove("foo"); + manager.remove("qux"); + manager.remove("bar"); + + } finally { + // manager.close(); // to see Close logs by remove + } + + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#new 4"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#close 2"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testClose() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new AsyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + var errors = manager.setup(); + assertThat(errors).hasSize(0); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + var ds4 = new AsyncDataSrc(4, logger, Fail.Not); + manager.add("qux", ds4); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(2); + assertThat(manager.listReady).hasSize(2); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(8); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#new 4"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupNoDataSrc() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setup(); + assertThat(errors).isEmpty(); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(0); + } finally { + manager.close(); + } + + assertThat(logger).isEmpty(); + var iter = logger.iterator(); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupAndOk() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new AsyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(2); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setup(); + assertThat(errors).hasSize(0); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(2); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(6); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("AsyncDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupButError() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new SyncDataSrc(2, logger, Fail.Setup); + manager.add("bar", ds2); + + var ds3 = new SyncDataSrc(3, logger, Fail.Setup); + manager.add("bar", ds3); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(3); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setup(); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(3); + assertThat(manager.listReady).hasSize(0); + + assertThat(errors).hasSize(1); + assertThat(errors.get(0).index).isEqualTo(1); + assertThat(errors.get(0).name).isEqualTo("bar"); + assertThat(errors.get(0).err.toString()) + .isEqualTo( + "com.github.sttk.errs.Err { reason = java.lang.String XXX, file = DataSrcManagerTest.java, line = 41 }"); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(6); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 2 failed"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupWithOrderNoDtaSrc() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setupWithOrder(List.of("bar", "foo")); + assertThat(errors).hasSize(0); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(0); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(0); + var iter = logger.iterator(); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupWithOrderAndOk() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new SyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(3); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setupWithOrder(List.of("bar", "foo", "xxx")); + assertThat(errors).hasSize(0); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(3); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(9); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 2"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupWithOrdrAndFail() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Setup); + manager.add("foo", ds1); + + var ds2 = new SyncDataSrc(2, logger, Fail.Setup); + manager.add("bar", ds2); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + var ds4 = new SyncDataSrc(4, logger, Fail.Not); + manager.add("qux", ds4); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(4); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setupWithOrder(List.of("qux", "baz", "foo")); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(4); + assertThat(manager.listReady).hasSize(0); + + assertThat(errors).hasSize(1); + assertThat(errors.get(0).index).isEqualTo(2); + assertThat(errors.get(0).name).isEqualTo("foo"); + assertThat(errors.get(0).err.toString()) + .isEqualTo( + "com.github.sttk.errs.Err { reason = java.lang.String XXX, file = DataSrcManagerTest.java, line = 41 }"); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(9); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 4"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 4"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1 failed"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 4"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupWithOrderContainingDuplicatedNameAndOk() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new SyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(3); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setupWithOrder(List.of("baz", "baz", "foo")); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(3); + + assertThat(errors).isEmpty(); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(9); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 3"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupWithOrderContainingDuplicatedNameAndOk2() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new SyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + var ds4 = new SyncDataSrc(4, logger, Fail.Not); + manager.add("qux", ds4); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(4); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setupWithOrder(List.of("baz", "foo", "baz", "qux")); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(4); + + assertThat(errors).isEmpty(); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(12); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 4"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 4"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 4"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 3"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void setupWithOrderButOneOfNamesIsNotUsed() { + var logger = new ArrayList(); + + var manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var ds2 = new SyncDataSrc(2, logger, Fail.Not); + manager.add("bar", ds2); + + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("baz", ds3); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(3); + assertThat(manager.listReady).hasSize(0); + + var errors = manager.setupWithOrder(List.of("baz", "foo", "xxx")); + + assertThat(manager.local).isTrue(); + assertThat(manager.listUnready).hasSize(0); + assertThat(manager.listReady).hasSize(3); + + assertThat(errors).isEmpty(); + } finally { + manager.close(); + } + + assertThat(logger).hasSize(9); + var iter = logger.iterator(); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#new 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 3"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#setup 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 2"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 1"); + assertThat(iter.next()).isEqualTo("SyncDataSrc#close 3"); + assertThat(iter.hasNext()).isFalse(); + } + + @Test + void testCopyDsReadyToMap() { + var logger = new ArrayList(); + + var contMap = new HashMap(); + + var manager = new DataSrcManager(true); + try { + manager.copyDsReadyToMap(contMap); + } finally { + manager.close(); + } + assertThat(contMap).isEmpty(); + + manager = new DataSrcManager(true); + try { + var ds1 = new SyncDataSrc(1, logger, Fail.Not); + manager.add("foo", ds1); + + var errors = manager.setup(); + assertThat(errors).isEmpty(); + + manager.copyDsReadyToMap(contMap); + } finally { + manager.close(); + } + assertThat(contMap).hasSize(1); + assertThat(contMap.get("foo").local).isTrue(); + assertThat(contMap.get("foo").name).isEqualTo("foo"); + + manager = new DataSrcManager(false); + try { + var ds2 = new AsyncDataSrc(2, logger, Fail.Not); + var ds3 = new SyncDataSrc(3, logger, Fail.Not); + manager.add("bar", ds2); + manager.add("baz", ds3); + + var errors = manager.setup(); + assertThat(errors).isEmpty(); + + manager.copyDsReadyToMap(contMap); + } finally { + manager.close(); + } + assertThat(contMap).hasSize(3); + assertThat(contMap.get("foo").local).isTrue(); + assertThat(contMap.get("foo").name).isEqualTo("foo"); + assertThat(contMap.get("bar").local).isFalse(); + assertThat(contMap.get("bar").name).isEqualTo("bar"); + assertThat(contMap.get("baz").local).isFalse(); + assertThat(contMap.get("baz").name).isEqualTo("baz"); + } +} diff --git a/src/test/java/com/github/sttk/sabi/internal/TxnFailureReportBuilderTest.java b/src/test/java/com/github/sttk/sabi/internal/TxnFailureReportBuilderTest.java new file mode 100644 index 0000000..f4e371f --- /dev/null +++ b/src/test/java/com/github/sttk/sabi/internal/TxnFailureReportBuilderTest.java @@ -0,0 +1,460 @@ +package com.github.sttk.sabi.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.sttk.errs.Err; +import com.github.sttk.sabi.AsyncGroup; +import com.github.sttk.sabi.DataConn; +import com.github.sttk.sabi.TxnFailureCause; +import com.github.sttk.sabi.TxnFailureCauseState; +import com.github.sttk.sabi.TxnFailureRecovery; +import com.github.sttk.sabi.TxnFailureRollback; +import com.github.sttk.sabi.TxnFailureRollbackState; +import org.junit.jupiter.api.Test; + +public class TxnFailureReportBuilderTest { + private TxnFailureReportBuilderTest() {} + + static class AbcDataConn implements DataConn { + @Override + public void commit(AsyncGroup ag) throws Err {} + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void rollback(AsyncGroup ag) {} + + @Override + public void close() {} + } + + @Test + void testBuilder() { + var builder = new TxnFailureReportBuilder("foo", AbcDataConn.class); + assertThat(builder.dataConnName).isEqualTo("foo"); + assertThat(builder.dataConnClass).isEqualTo(AbcDataConn.class); + assertThat(builder.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(builder.cause.err).isNull(); + assertThat(builder.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(builder.rollback.err).isNull(); + + var report = builder.build(); + assertThat(report.dataConnName).isEqualTo("foo"); + assertThat(report.dataConnType) + .isEqualTo("com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn"); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.cause.err).isNull(); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.rollback.err).isNull(); + + builder.cause = new TxnFailureCause(TxnFailureCauseState.CommitFailure, new Err("fail")); + builder.rollback = + new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, new Err("x")); + + report = builder.build(); + assertThat(report.dataConnName).isEqualTo("foo"); + assertThat(report.dataConnType) + .isEqualTo("com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn"); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.cause.err.getReason()).isEqualTo("fail"); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.rollback.err.getReason()).isEqualTo("x"); + } + + @Test + void testIsCauseOfFailure() { + var builder = new TxnFailureReportBuilder("foo", AbcDataConn.class); + assertThat(builder.dataConnName).isEqualTo("foo"); + assertThat(builder.dataConnClass).isEqualTo(AbcDataConn.class); + assertThat(builder.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(builder.cause.err).isNull(); + assertThat(builder.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(builder.rollback.err).isNull(); + + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.isCauseOfFailure()).isFalse(); + + builder.cause = new TxnFailureCause(TxnFailureCauseState.NoneByCommitted, new Err("fail")); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.isCauseOfFailure()).isFalse(); + + builder.cause = new TxnFailureCause(TxnFailureCauseState.LogicFailure, new Err("fail")); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.isCauseOfFailure()).isTrue(); + + builder.cause = new TxnFailureCause(TxnFailureCauseState.CommitFailure, new Err("fail")); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.isCauseOfFailure()).isTrue(); + + builder.cause = new TxnFailureCause(TxnFailureCauseState.PostCommitFailure, new Err("fail")); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.isCauseOfFailure()).isTrue(); + } + + @Test + void testRecoveryForCommit() { + var builder = new TxnFailureReportBuilder("foo", AbcDataConn.class); + assertThat(builder.dataConnName).isEqualTo("foo"); + assertThat(builder.dataConnClass).isEqualTo(AbcDataConn.class); + + assertThat(builder.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForCommit()).isEqualTo(TxnFailureRecovery.RerunLogicAndCommit); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.ResolveCauseAndInconsistency); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.NoneByCommitted, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForCommit()).isEqualTo(TxnFailureRecovery.NoActionRequired); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.LogicFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.ResolveCauseThenRerunLogicAndCommit); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.ResolveCauseAndInconsistency); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.CommitFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.ResolveCauseThenRerunLogicAndCommit); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.ResolveCauseAndInconsistency); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.PostCommitFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.ResolveCauseThenRerunPostCommit); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForCommit()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + } + } + + @Test + void testRecoveryForRollback() { + var builder = new TxnFailureReportBuilder("foo", AbcDataConn.class); + assertThat(builder.dataConnName).isEqualTo("foo"); + assertThat(builder.dataConnClass).isEqualTo(AbcDataConn.class); + + assertThat(builder.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForRollback()).isEqualTo(TxnFailureRecovery.NoActionRequired); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.ResolveCauseAndInconsistency); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.NoneByCommitted, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForRollback()).isEqualTo(TxnFailureRecovery.ManualRollbackRequired); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.NoneByCommitted); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.LogicFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForRollback()).isEqualTo(TxnFailureRecovery.NoActionRequired); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.LogicFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.ResolveCauseAndInconsistency); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.CommitFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForRollback()).isEqualTo(TxnFailureRecovery.NoActionRequired); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.CommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.ResolveCauseAndInconsistency); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.PostCommitFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByNotRolledBack); + assertThat(report.recoveryForRollback()).isEqualTo(TxnFailureRecovery.ManualRollbackRequired); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.NoneByRolledBack); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.cause.state).isEqualTo(TxnFailureCauseState.PostCommitFailure); + assertThat(report.rollback.state).isEqualTo(TxnFailureRollbackState.RollbackFailure); + assertThat(report.recoveryForRollback()) + .isEqualTo(TxnFailureRecovery.InvestigateBecauseImpossible); + } + } + + @Test + void testToString() { + var builder = new TxnFailureReportBuilder("foo", AbcDataConn.class); + assertThat(builder.dataConnName).isEqualTo("foo"); + assertThat(builder.dataConnClass).isEqualTo(AbcDataConn.class); + + assertThat(builder.cause.state).isEqualTo(TxnFailureCauseState.NoneByUncommitted); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:NoneByUncommitted Err:null} rollback:{State:RollbackFailure Err:null}}"); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.NoneByCommitted, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByNotRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:NoneByRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:NoneByCommitted Err:null} rollback:{State:RollbackFailure Err:null}}"); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.LogicFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:LogicFailure Err:null} rollback:{State:NoneByNotRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:LogicFailure Err:null} rollback:{State:NoneByRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:LogicFailure Err:null} rollback:{State:RollbackFailure Err:null}}"); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.CommitFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:CommitFailure Err:null} rollback:{State:NoneByNotRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:CommitFailure Err:null} rollback:{State:NoneByRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:CommitFailure Err:null} rollback:{State:RollbackFailure Err:null}}"); + } + + builder.cause = new TxnFailureCause(TxnFailureCauseState.PostCommitFailure, null); + { + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByNotRolledBack, null); + var report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:PostCommitFailure Err:null} rollback:{State:NoneByNotRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.NoneByRolledBack, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:PostCommitFailure Err:null} rollback:{State:NoneByRolledBack Err:null}}"); + + builder.rollback = new TxnFailureRollback(TxnFailureRollbackState.RollbackFailure, null); + report = builder.build(); + assertThat(report.toString()) + .isEqualTo( + "{dataConnName:foo dataConnType:com.github.sttk.sabi.internal.TxnFailureReportBuilderTest$AbcDataConn cause:{State:PostCommitFailure Err:null} rollback:{State:RollbackFailure Err:null}}"); + } + } +} diff --git a/src/test/resources/META-INF/native-image/native-image.properties b/src/test/resources/META-INF/native-image/native-image.properties deleted file mode 100644 index bfd2b8e..0000000 --- a/src/test/resources/META-INF/native-image/native-image.properties +++ /dev/null @@ -1,20 +0,0 @@ -Args = --initialize-at-build-time=\ - org.junit.jupiter.api.DisplayNameGenerator\$IndicativeSentences,\ - org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor\$ClassInfo,\ - org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor\$LifecycleMethods,\ - org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor,\ - org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor,\ - org.junit.jupiter.engine.descriptor.DynamicDescendantFilter\$Mode,\ - org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector\$1,\ - org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor\$MethodInfo,\ - org.junit.jupiter.engine.discovery.ClassSelectorResolver\$DummyClassTemplateInvocationContext,\ - org.junit.platform.engine.support.store.NamespacedHierarchicalStore\$EvaluatedValue,\ - org.junit.platform.launcher.core.DiscoveryIssueNotifier,\ - org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider,\ - org.junit.platform.launcher.core.LauncherDiscoveryResult\$EngineResultInfo,\ - org.junit.platform.launcher.core.LauncherPhase,\ - org.junit.platform.suite.engine.DiscoverySelectorResolver,\ - org.junit.platform.suite.engine.SuiteTestDescriptor\$DiscoveryIssueForwardingListener,\ - org.junit.platform.suite.engine.SuiteTestDescriptor\$LifecycleMethods,\ - org.junit.platform.commons.util.KotlinReflectionUtils,\ - org.junit.platform.launcher.core.DiscoveryIssueNotifier\$1 \ No newline at end of file