This is an automated email from the ASF dual-hosted git repository.
veithen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-axiom.git
The following commit(s) were added to refs/heads/master by this push:
new 952ef54df Add unit tests for union-checker
952ef54df is described below
commit 952ef54dfadb5e2140faf28d0a0b80d8015d693a
Author: Andreas Veithen-Knowles <[email protected]>
AuthorDate: Mon Jun 15 08:47:38 2026 +0100
Add unit tests for union-checker
Use the Checker Framework's CheckerFrameworkPerDirectoryTest harness to
compile small test sources under tests/union/ with the UnionChecker
attached, asserting the expected diagnostics via inline // :: error:
comments. Covers subset-based subtyping, instanceof narrowing, the
@UnknownUnion escape rule, conditional expression LUB/promotion, and
null/@UnionBottom handling.
Co-authored-by: Claude <[email protected]>
---
buildutils/union-checker/pom.xml | 37 ++++++++++++++
.../axiom/checker/union/UnionCheckerTest.java | 37 ++++++++++++++
.../union-checker/tests/union/ConditionalLub.java | 52 ++++++++++++++++++++
.../tests/union/ConditionalPromotion.java | 52 ++++++++++++++++++++
.../tests/union/InstanceofNarrowing.java | 56 ++++++++++++++++++++++
.../union-checker/tests/union/NullAssignment.java | 32 +++++++++++++
.../union-checker/tests/union/Subtyping.java | 44 +++++++++++++++++
.../tests/union/UnknownUnionEscape.java | 48 +++++++++++++++++++
8 files changed, 358 insertions(+)
diff --git a/buildutils/union-checker/pom.xml b/buildutils/union-checker/pom.xml
index 51ad2dd6e..8fb2af930 100644
--- a/buildutils/union-checker/pom.xml
+++ b/buildutils/union-checker/pom.xml
@@ -44,5 +44,42 @@
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>framework-test</artifactId>
+ <version>${checkerframework.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!-- The tests invoke the javac compiler API directly,
which requires access
+ to javac internals not exported by the jdk.compiler
module. -->
+ <argLine>
+ --add-opens
jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+ --add-opens
jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
+ </argLine>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git
a/buildutils/union-checker/src/test/java/org/apache/axiom/checker/union/UnionCheckerTest.java
b/buildutils/union-checker/src/test/java/org/apache/axiom/checker/union/UnionCheckerTest.java
new file mode 100644
index 000000000..ceb216d50
--- /dev/null
+++
b/buildutils/union-checker/src/test/java/org/apache/axiom/checker/union/UnionCheckerTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.checker.union;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Runs the {@link UnionChecker} against the test sources in {@code
tests/union}. */
+public class UnionCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+ public UnionCheckerTest(List<File> testFiles) {
+ super(testFiles, UnionChecker.class, "union", "-Anomsgtext");
+ }
+
+ @Parameters
+ public static String[] getTestDirs() {
+ return new String[] {"union"};
+ }
+}
diff --git a/buildutils/union-checker/tests/union/ConditionalLub.java
b/buildutils/union-checker/tests/union/ConditionalLub.java
new file mode 100644
index 000000000..e53aa0d7a
--- /dev/null
+++ b/buildutils/union-checker/tests/union/ConditionalLub.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/**
+ * Tests for the least upper bound of two {@code @Union} qualifiers at a
conditional expression
+ * ({@code UnionQualifierHierarchy#leastUpperBoundWithElements}).
+ */
+class ConditionalLub {
+
+ static class A {}
+
+ static class B {}
+
+ void acceptAOrB(@Union(types = {A.class, B.class}) Object o) {}
+
+ void acceptAOnly(@Union(types = {A.class}) Object o) {}
+
+ // The lub of @Union({A}) and @Union({B}) is @Union({A, B}).
+ void lub(
+ boolean flag,
+ @Union(types = {A.class}) Object a,
+ @Union(types = {B.class}) Object b) {
+ acceptAOrB(flag ? a : b);
+ }
+
+ // @Union({A, B}) is not a subtype of @Union({A}).
+ void lubNotNarrower(
+ boolean flag,
+ @Union(types = {A.class}) Object a,
+ @Union(types = {B.class}) Object b) {
+ // :: error: (argument)
+ acceptAOnly(flag ? a : b);
+ }
+}
diff --git a/buildutils/union-checker/tests/union/ConditionalPromotion.java
b/buildutils/union-checker/tests/union/ConditionalPromotion.java
new file mode 100644
index 000000000..839e2ad1b
--- /dev/null
+++ b/buildutils/union-checker/tests/union/ConditionalPromotion.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/**
+ * Tests for promoting the type of a conditional expression to {@code
@Union(types = {...})} when
+ * one branch is {@code @UnknownUnion} but its erased type is a subtype of a
union member
+ * ({@code UnionTreeAnnotator}).
+ */
+class ConditionalPromotion {
+
+ // sb has erased type StringBuilder, a subtype of CharSequence, so the
conditional
+ // expression is promoted to @Union({String, CharSequence}), regardless of
which branch it
+ // appears in.
+ @Union(types = {String.class, CharSequence.class}) Object unknownSecond(
+ boolean flag,
+ @Union(types = {String.class, CharSequence.class}) Object data,
StringBuilder sb) {
+ return flag ? sb : data;
+ }
+
+ @Union(types = {String.class, CharSequence.class}) Object unknownFirst(
+ boolean flag,
+ @Union(types = {String.class, CharSequence.class}) Object data,
StringBuilder sb) {
+ return flag ? data : sb;
+ }
+
+ // other has erased type Object, which is not a subtype of String or
CharSequence, so no
+ // promotion happens and the conditional expression's type remains
@UnknownUnion, which is not
+ // a subtype of the declared @Union return type.
+ @Union(types = {String.class, CharSequence.class}) Object noPromotion(
+ boolean flag, @Union(types = {String.class, CharSequence.class})
Object data, Object other) {
+ // :: error: (return)
+ return flag ? other : data;
+ }
+}
diff --git a/buildutils/union-checker/tests/union/InstanceofNarrowing.java
b/buildutils/union-checker/tests/union/InstanceofNarrowing.java
new file mode 100644
index 000000000..0b9679ba2
--- /dev/null
+++ b/buildutils/union-checker/tests/union/InstanceofNarrowing.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/** Tests for {@code instanceof}-based narrowing ({@code UnionTransfer}). */
+class InstanceofNarrowing {
+
+ void acceptStringOrInteger(@Union(types = {String.class, Integer.class})
Object o) {}
+
+ void acceptIntegerOnly(@Union(types = {Integer.class}) Object o) {}
+
+ // x instanceof String || x instanceof Integer narrows x to
@Union({String, Integer}).
+ void or(Object x) {
+ if (x instanceof String || x instanceof Integer) {
+ acceptStringOrInteger(x);
+ }
+ }
+
+ // The equivalent if/else if chain narrows x the same way in each branch.
+ void elseIf(Object x) {
+ if (x instanceof String) {
+ acceptStringOrInteger(x);
+ } else if (x instanceof Integer) {
+ acceptStringOrInteger(x);
+ } else {
+ // :: error: (argument)
+ acceptStringOrInteger(x);
+ }
+ }
+
+ // x instanceof String narrows x to @Union({String}), which is not a
subtype of
+ // @Union({Integer}).
+ void mismatch(Object x) {
+ if (x instanceof String) {
+ // :: error: (argument)
+ acceptIntegerOnly(x);
+ }
+ }
+}
diff --git a/buildutils/union-checker/tests/union/NullAssignment.java
b/buildutils/union-checker/tests/union/NullAssignment.java
new file mode 100644
index 000000000..8d274c090
--- /dev/null
+++ b/buildutils/union-checker/tests/union/NullAssignment.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/** Tests that {@code null} ({@code @UnionBottom}) is assignable to any {@code
@Union} type. */
+class NullAssignment {
+
+ @Union(types = {String.class, Integer.class}) Object field = null;
+
+ void acceptStringOrInteger(@Union(types = {String.class, Integer.class})
Object o) {}
+
+ void test() {
+ acceptStringOrInteger(null);
+ }
+}
diff --git a/buildutils/union-checker/tests/union/Subtyping.java
b/buildutils/union-checker/tests/union/Subtyping.java
new file mode 100644
index 000000000..7b291e508
--- /dev/null
+++ b/buildutils/union-checker/tests/union/Subtyping.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/** Tests for {@code @Union} subset-based subtyping ({@code
UnionQualifierHierarchy}). */
+class Subtyping {
+
+ void acceptNarrow(@Union(types = {String.class}) Object o) {}
+
+ void acceptWide(@Union(types = {String.class, Integer.class}) Object o) {}
+
+ // @Union({String}) is a subtype of @Union({String, Integer}).
+ void narrowToWide(@Union(types = {String.class}) Object narrow) {
+ acceptWide(narrow);
+ }
+
+ // @Union({String, Integer}) is not a subtype of @Union({String}).
+ void wideToNarrow(@Union(types = {String.class, Integer.class}) Object
wide) {
+ // :: error: (argument)
+ acceptNarrow(wide);
+ }
+
+ // Equal sets are subtypes of each other.
+ void sameSet(@Union(types = {Integer.class, String.class}) Object wide) {
+ acceptWide(wide);
+ }
+}
diff --git a/buildutils/union-checker/tests/union/UnknownUnionEscape.java
b/buildutils/union-checker/tests/union/UnknownUnionEscape.java
new file mode 100644
index 000000000..529253f04
--- /dev/null
+++ b/buildutils/union-checker/tests/union/UnknownUnionEscape.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/**
+ * Tests for the {@code @UnknownUnion} escape rule, which allows an
unconstrained value to be used
+ * where {@code @Union(types = {...})} is expected if its erased type is a
subtype of one of the
+ * union members ({@code UnionTypeHierarchy}).
+ */
+class UnknownUnionEscape {
+
+ void acceptStringOrCharSequence(@Union(types = {String.class,
CharSequence.class}) Object o) {}
+
+ void acceptIntegerOnly(@Union(types = {Integer.class}) Object o) {}
+
+ // String is itself one of the union members.
+ void exactMember(String s) {
+ acceptStringOrCharSequence(s);
+ }
+
+ // StringBuilder is a subtype of CharSequence, a union member.
+ void subtypeOfMember(StringBuilder sb) {
+ acceptStringOrCharSequence(sb);
+ }
+
+ // StringBuilder is not a subtype of Integer, the only union member.
+ void notAMember(StringBuilder sb) {
+ // :: error: (argument)
+ acceptIntegerOnly(sb);
+ }
+}