JDBC: Automate schema version test discovery#4583
Conversation
dimas-b
left a comment
There was a problem hiding this comment.
Thanks for your contribution, @venkateshwaracholan !
|
|
||
| @Override | ||
| public int schemaVersion() { | ||
| return ${schemaVersion}; |
There was a problem hiding this comment.
This is an interesting idea... but it feels a bit like an overkill to me 😅
Could we not do the same with JUnit test factories and without on-the-fly code generation?
There was a problem hiding this comment.
+1, using TestFactory and DynamicTests sounds like a better approach.
There was a problem hiding this comment.
Thanks @dimas-b @nandorKollar, good call. I dropped the Gradle codegen and switched to @testfactory / dynamic tests instead.
| ClassLoader classLoader = H2SchemaVersions.class.getClassLoader(); | ||
| SortedSet<Integer> versions = new TreeSet<>(); | ||
| for (int version = 0; ; version++) { | ||
| String resource = String.format("h2/schema-v%d.sql", version); |
There was a problem hiding this comment.
nit: I'd keep the the String.format which is used in the actual schema executor code in AtomicMetastoreManagerWithJdbcBasePersistenceImplTest base class:
String.format("%s/schema-v%d.sql", DatabaseType.H2.getDisplayName(), version)
| SortedSet<Integer> versions = new TreeSet<>(); | ||
| for (int version = 0; ; version++) { | ||
| String resource = String.format("h2/schema-v%d.sql", version); | ||
| if (classLoader.getResource(resource) == null) { |
There was a problem hiding this comment.
nit: this will skip schema files, when the numbering is not continuous, like for example: schema-0, schema-1, schema-3. Not sure that this we should prepare for this scenario too, or should we?
There was a problem hiding this comment.
+1 to @nandorKollar 's comment and it's not a "nit" IMHO :)
I'd check every version number until DatabaseType.getLatestSchemaVersion() and fail if any version is missing the corresponding schema file resource.
|
Although the current approach works, I don't really like the reflective invocation and the fact, that we need to manually implement BeforeEach. I think we can simplify that part using ParmetrizedTestClass. Instead of dynamics tests, how about something like this: Would this work? Does it look better than dynamic tests? |
|
|
||
| @TestFactory | ||
| Stream<DynamicNode> metastoreTestsForAllH2SchemaVersions() { | ||
| return H2SchemaVersions.discover().stream().map(this::testsForSchemaVersion); |
There was a problem hiding this comment.
for later: I'd rather we did this testing with PostgreSQL, which is the "main" database 🤔 ... H2 is not expected to be used in "prod", so full schema coverage on H2 does not seem to add assurance for end users 🤔
There was a problem hiding this comment.
Should we open a followup issue for that? I think Postgres already has a testcontainer, it seems that we already use it in Polaris integration tests.
There was a problem hiding this comment.
Yes, let's keep this PR as "pure" refactoring and change from H2 to Posgres later.
| private static void runTest(Method method, int schemaVersion) throws Exception { | ||
| AtomicMetastoreManagerWithJdbcBasePersistenceImplTest testInstance = | ||
| new SchemaVersionTest(schemaVersion); | ||
| testInstance.setupPolarisMetaStoreManager(); | ||
| method.setAccessible(true); | ||
| method.invoke(testInstance); |
There was a problem hiding this comment.
This is flaky, IMHO, because we provide only a very narrow sub-set of functionality that @Test method may expect from the JUnit framework... but overall, it's probably ok for now.
There was a problem hiding this comment.
@dimas-b that's why I thought that maybe using ParameterizedClass could be a better choice here.
There was a problem hiding this comment.
+1 to try ParameterizedClass... TIL that it came with JUnit 6 😅
There was a problem hiding this comment.
Actually it seems that it is there since JUnit 5.13: https://docs.junit.org/5.13.0/api/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedClass.html!
| SortedSet<Integer> versions = new TreeSet<>(); | ||
| for (int version = 0; ; version++) { | ||
| String resource = String.format("h2/schema-v%d.sql", version); | ||
| if (classLoader.getResource(resource) == null) { |
There was a problem hiding this comment.
+1 to @nandorKollar 's comment and it's not a "nit" IMHO :)
I'd check every version number until DatabaseType.getLatestSchemaVersion() and fail if any version is missing the corresponding schema file resource.
|
Thanks @dimas-b and @nandorKollar for the feedback. Switched from @TestFactory/reflection to @ParameterizedClass, so the inherited tests run with the normal JUnit lifecycle. |
| String resource = | ||
| String.format("%s/schema-v%d.sql", DatabaseType.H2.getDisplayName(), version); | ||
| if (classLoader.getResource(resource) == null) { | ||
| missingVersions.add(version); |
There was a problem hiding this comment.
nit: this looks good, but what if we make it simpler. We can simply use DatabaseType.getLatestSchemaVersion() to create the parameters from 0..DatabaseType.getLatestSchemaVersion()-1, and parametrise the test with that number. The test itself will try to load the file, and if let's say v2 will be missing, then it will eventually fail, as we expect. Maybe the failure report isn't that elegant as with explicitly checking for the continuous versioning. Basically we don't even need this class, but
static Stream<Integer> schemaVersions() {
return IntStream.rangeClosed(0, DatabaseType.getLatestSchemaVersion());
}
What do you think?
Fixes #2675
Summary
Run JDBC metastore integration tests against every H2 schema version discovered on the test classpath without maintaining a separate test class per schema version.
Changes
H2SchemaVersionsto discover availableh2/schema-vN.sqlresources from the test classpath.AtomicMetastoreManagerWithJdbcBasePersistenceImplSchemaTestusing JUnit@TestFactory,DynamicContainer, andDynamicTestto execute the JDBC metastore test suite against each discovered schema version.V0throughV4).Validation
Ran:
./gradlew :polaris-relational-jdbc:test \ --tests "org.apache.polaris.persistence.relational.jdbc.AtomicMetastoreManagerWithJdbcBasePersistenceImplV*SchemaTest"Checklist
CHANGELOG.md(if needed)site/content/in-dev/unreleased(if needed)