Skip to content

Problematic use of generic types #238

@ldcasillas-progreso

Description

@ldcasillas-progreso

Using TestContainers 1.1.6, the following code produces a compile time error:

private static final PostgreSQLContainer postgres =
        new PostgreSQLContainer("postgres:9.5.4")
                .withDatabaseName("test")
                .withEnv("PGDATA", CONTAINER_PGDATA)
                .withFileSystemBind(HOST_PGDATA.toString(), CONTAINER_PGDATA, BindMode.READ_WRITE);

Compilation error:

/Users/my-user-name/my-project/my/path/to/IntegrationTest.java:71: error: incompatible types: GenericContainer cannot be converted to PostgreSQLContainer
                    .withFileSystemBind(HOST_PGDATA.toString(), CONTAINER_PGDATA, BindMode.READ_WRITE)

The problem stems from the fact that the PostgreSQLContainer class starts like this, with a recursive bound on its generic type parameter (à la class Enum<E extends Enum<E>>):

public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>> 
        extends JdbcDatabaseContainer<SELF>

So this is a generic class, but the documentation and example test case use it with no type parameter, as a raw type:

@Rule
public PostgreSQLContainer postgres = new PostgreSQLContainer();

And in this situation Java can't figure out that in my use of the withFileSytemBind() method, the SELF return type unifies with PostgreSQLContainer.

My workaround for this issue illustrates the fix:

// A dummy class whose sole purpose is to instantiate the `SELF`
// type parameter.
private static final class MyPostgreSQLContainer extends PostgreSQLContainer<MyPostgreSQLContainer> {
    public MyPostgreSQLContainer(String dockerImageName) {
        super(dockerImageName);
    }
}

private static final MyPostgreSQLContainer postgres =
        new MyPostgreSQLContainer("postgres:9.5.4")
                .withDatabaseName("test")
                .withEnv("PGDATA", CONTAINER_PGDATA)
                .withFileSystemBind(HOST_PGDATA.toString(), CONTAINER_PGDATA, BindMode.READ_WRITE);

This argues that the existing "end user facing" classes like PostgreSQLContainer in the library ought to be split into two classes:

public abstract class AbstractPostgreSQLContainer<SELF extends AbstractPostgreSQLContainer<SELF>> 
        extends JdbcDatabaseContainer<SELF> {
    ...
}

public class PostgreSQLContainer extends AbstractPostgreSQLContainer<PostgreSQLContainer> {
    ...
}

People who want to extend PostgreSQLContainer should be discouraged from doing so as well, and pointed to AbstractPostgreSQLContainer as the proper class for extension. (Making PostgreSQLContainer a final class is one alternative here, but with the downside that it's a backwards-incompatible change.)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions