• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aas-core-works / aas-core-codegen / 23339141012

20 Mar 2026 10:37AM UTC coverage: 83.911% (+0.2%) from 83.702%
23339141012

push

github

web-flow
Add generation of test code for Java (#599)

We add the unit test generators for the Java SDK. This greatly simplifies our development pipeline.

435 of 450 new or added lines in 30 files covered. (96.67%)

4 existing lines in 1 file now uncovered.

31183 of 37162 relevant lines covered (83.91%)

3.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.93
/aas_core_codegen/java/tests/_generate_test_copying.py
1
"""Generate code to test copying."""
2

3
import io
4✔
4
import textwrap
4✔
5
from typing import List, Optional
4✔
6

7
from aas_core_codegen import intermediate
4✔
8
from aas_core_codegen.common import (
4✔
9
    Stripped,
10
    indent_but_first_line,
11
    assert_never,
12
    Identifier,
13
)
14
from aas_core_codegen.java import common as java_common, naming as java_naming
4✔
15
from aas_core_codegen.java.common import INDENT as I, INDENT2 as II, INDENT3 as III
4✔
16

17

18
def _generate_shallow_equals(cls: intermediate.ConcreteClass) -> Stripped:
4✔
19
    """Generate the code for a static shallow ``Equals`` method."""
20
    if cls.is_implementation_specific:
4✔
NEW
21
        raise AssertionError(
×
22
            f"(empwilli):"
23
            f"the class {cls.name!r} is implementation specific. "
24
            f"at the moment, we assume that all classes are not "
25
            f"implementation-specific, so that we can automatically generate the "
26
            f"shallow-equals methods. this way we can dispense of the whole "
27
            f"snippet/specific-implementation loading logic in "
28
            f"the unit test generation. please notify the developers if you see this, "
29
            f"so that we can add the logic for implementation-specific classes "
30
            f"to this generation script."
31
        )
32

33
    exprs = []  # type: List[str]
4✔
34
    for prop in cls.properties:
4✔
35
        prop_name = java_naming.method_name(Identifier(f"get_{prop.name}"))
4✔
36
        exprs.append(f"that.{prop_name}().equals(other.{prop_name}())")
4✔
37

38
    # NOTE (empwilli):
39
    # This is a poor man's line re-flowing.
40
    exprs_joined = " && ".join(exprs)
4✔
41
    if len(exprs_joined) < 70:
4✔
42
        statement = Stripped(f"return {exprs_joined};")
4✔
43
    else:
44
        exprs_joined = "\n&& ".join(exprs)
4✔
45
        statement = Stripped(
4✔
46
            f"""\
47
return (
48
{I}{indent_but_first_line(exprs_joined, I)});"""
49
        )
50

51
    cls_name_java = java_naming.class_name(cls.name)
4✔
52

53
    return Stripped(
4✔
54
        f"""\
55
private static Boolean {cls_name_java}ShallowEquals(
56
{I}{cls_name_java} that,
57
{I}{cls_name_java} other) {{
58
{I}{indent_but_first_line(statement, I)}
59
}}"""
60
    )
61

62

63
def _generate_transform_as_deep_equals(cls: intermediate.ConcreteClass) -> Stripped:
4✔
64
    """Generate the transform method that checks for deep equality."""
65
    if cls.is_implementation_specific:
4✔
NEW
66
        raise AssertionError(
×
67
            f"(empwilli): "
68
            f"The class {cls.name!r} is implementation specific. "
69
            f"At the moment, we assume that all classes are not "
70
            f"implementation-specific, so that we can automatically generate the "
71
            f"shallow-equals methods. This way we can dispense of the whole "
72
            f"snippet/specific-implementation loading logic in "
73
            f"the unit test generation. Please notify the developers if you see this, "
74
            f"so that we can add the logic for implementation-specific classes "
75
            f"to this generation script."
76
        )
77

78
    cls_name = java_naming.class_name(cls.name)
4✔
79

80
    exprs = []  # type: List[Stripped]
4✔
81

82
    for prop in cls.properties:
4✔
83
        optional = isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation)
4✔
84
        type_anno = intermediate.beneath_optional(prop.type_annotation)
4✔
85
        getter_name = java_naming.getter_name(prop.name)
4✔
86
        primitive_type = intermediate.try_primitive_type(type_anno)
4✔
87

88
        expr = None  # type: Optional[Stripped]
4✔
89

90
        if isinstance(type_anno, intermediate.PrimitiveTypeAnnotation) or (
4✔
91
            isinstance(type_anno, intermediate.OurTypeAnnotation)
92
            and isinstance(type_anno.our_type, intermediate.ConstrainedPrimitive)
93
        ):
94
            assert primitive_type is not None
4✔
95
            if (
4✔
96
                primitive_type is intermediate.PrimitiveType.BOOL
97
                or primitive_type is intermediate.PrimitiveType.INT
98
                or primitive_type is intermediate.PrimitiveType.FLOAT
99
                or primitive_type is intermediate.PrimitiveType.STR
100
            ):
101
                expr = Stripped(
4✔
102
                    f"""\
103
that.{getter_name}().equals(casted.{getter_name}())"""
104
                )
105
            elif primitive_type is intermediate.PrimitiveType.BYTEARRAY:
4✔
106
                expr = Stripped(
4✔
107
                    f"""\
108
Arrays.equals(
109
{I}that.{getter_name}().get(),
110
{I}casted.{getter_name}().get())"""
111
                )
112
            else:
NEW
113
                assert_never(primitive_type)
×
114

115
        elif isinstance(type_anno, intermediate.OurTypeAnnotation):
4✔
116
            if isinstance(type_anno.our_type, intermediate.Enumeration):
4✔
117
                expr = Stripped(
4✔
118
                    f"""\
119
that.{getter_name}().equals(casted.{getter_name}())"""
120
                )
121
            elif isinstance(type_anno.our_type, intermediate.ConstrainedPrimitive):
4✔
NEW
122
                raise AssertionError("Expected to handle this case above")
×
123
            elif isinstance(
4✔
124
                type_anno.our_type,
125
                (intermediate.AbstractClass, intermediate.ConcreteClass),
126
            ):
127
                if optional:
4✔
128
                    expr = Stripped(
4✔
129
                        f"""\
130
(that.{getter_name}().isPresent()
131
{I}? casted.{getter_name}().isPresent()
132
{I}&& transform( that.{getter_name}().get(), casted.{getter_name}().get())
133
{I}: ! casted.{getter_name}().isPresent())"""
134
                    )
135
                else:
136
                    expr = Stripped(
4✔
137
                        f"""\
138
transform(
139
{I}that.{getter_name}(),
140
{I}casted.{getter_name}())"""
141
                    )
142
        elif isinstance(type_anno, intermediate.ListTypeAnnotation):
4✔
143
            assert isinstance(
4✔
144
                type_anno.items, intermediate.OurTypeAnnotation
145
            ) and isinstance(type_anno.items.our_type, intermediate.Class), (
146
                f"(empwilli): We handle only lists of classes in the deep "
147
                f"equality checks at the moment. The meta-model does not contain "
148
                f"any other lists, so we wanted to keep the code as simple as "
149
                f"possible, and avoid unrolling. Please contact the developers "
150
                f"if you need this feature. The class in question was {cls.name!r} and "
151
                f"the property {prop.name!r}."
152
            )
153
            expr = Stripped(
4✔
154
                f"""\
155
that.{getter_name}().equals(casted.{getter_name}())"""
156
            )
157
        else:
158
            # noinspection PyTypeChecker
NEW
159
            assert_never(type_anno)
×
160

161
        exprs.append(expr)
4✔
162

163
    body_writer = io.StringIO()
4✔
164
    body_writer.write("return (")
4✔
165
    for i, expr in enumerate(exprs):
4✔
166
        body_writer.write("\n")
4✔
167
        if i > 0:
4✔
168
            body_writer.write(f"{I}&& {indent_but_first_line(expr, I)}")
4✔
169
        else:
170
            body_writer.write(f"{I}{indent_but_first_line(expr, I)}")
4✔
171

172
    body_writer.write(");")
4✔
173

174
    interface_name = java_naming.interface_name(cls.name)
4✔
175
    transform_name = java_naming.method_name(Identifier(f"transform_{cls.name}"))
4✔
176

177
    return Stripped(
4✔
178
        f"""\
179
@Override
180
public Boolean {transform_name}({interface_name} that, IClass other) {{
181
{I}if (!(other instanceof {cls_name})) {{
182
{II}return false;
183
{I}}}
184

185
{I}{cls_name} casted = ({cls_name}) that;
186

187
{I}{indent_but_first_line(body_writer.getvalue(), I)}
188
}}"""
189
    )
190

191

192
def _generate_deep_equals_transformer(
4✔
193
    symbol_table: intermediate.SymbolTable,
194
) -> Stripped:
195
    """Generate the transformer that checks for deep equality."""
196
    blocks = []  # type: List[Stripped]
4✔
197

198
    for concrete_cls in symbol_table.concrete_classes:
4✔
199
        if concrete_cls.is_implementation_specific:
4✔
NEW
200
            raise AssertionError(
×
201
                f"(empwilli): "
202
                f"The class {concrete_cls.name!r} is implementation specific. "
203
                f"At the moment, we assume that all classes are not "
204
                f"implementation-specific, so that we can automatically generate the "
205
                f"deep-equals methods. This way we can dispense of the whole "
206
                f"snippet/specific-implementation loading logic in "
207
                f"the unit test generation. Please notify the developers if you see "
208
                f"this, so that we can add the logic for implementation-specific "
209
                f"classes to this generation script."
210
            )
211

212
        blocks.append(_generate_transform_as_deep_equals(cls=concrete_cls))
4✔
213

214
    writer = io.StringIO()
4✔
215
    writer.write(
4✔
216
        """\
217
private static class DeepEqualiser extends AbstractTransformerWithContext<IClass, Boolean> {
218
"""
219
    )
220

221
    for i, block in enumerate(blocks):
4✔
222
        if i > 0:
4✔
223
            writer.write("\n\n")
4✔
224

225
        writer.write(textwrap.indent(block, I))
4✔
226

227
    writer.write("\n} // class DeepEqualiser")
4✔
228

229
    return Stripped(writer.getvalue())
4✔
230

231

232
def _generate_deep_equals(cls: intermediate.ConcreteClass) -> Stripped:
4✔
233
    """Generate the code for a static deep ``Equals`` method."""
234
    if cls.is_implementation_specific:
4✔
NEW
235
        raise AssertionError(
×
236
            f"(empwilli): "
237
            f"The class {cls.name!r} is implementation specific. "
238
            f"At the moment, we assume that all classes are not "
239
            f"implementation-specific, so that we can automatically generate the "
240
            f"shallow-equals methods. This way we can dispense of the whole "
241
            f"snippet/specific-implementation loading logic in "
242
            f"the unit test generation. Please notify the developers if you see this, "
243
            f"so that we can add the logic for implementation-specific classes "
244
            f"to this generation script."
245
        )
246

247
    cls_name = java_naming.class_name(cls.name)
4✔
248

249
    return Stripped(
4✔
250
        f"""\
251
private static Boolean {cls_name}DeepEquals({cls_name} that, {cls_name} other) {{
252
{I}return DeepEqualiserInstance.transform(that, other);
253
}}"""
254
    )
255

256

257
def generate(
4✔
258
    package: java_common.PackageIdentifier,
259
    symbol_table: intermediate.SymbolTable,
260
) -> List[java_common.JavaFile]:
261
    """
262
    Generate code to test copying.
263
    """
264
    blocks = [
4✔
265
        _generate_deep_equals_transformer(symbol_table=symbol_table),
266
        Stripped(
267
            """\
268
private static final DeepEqualiser DeepEqualiserInstance = new DeepEqualiser();"""
269
        ),
270
        Stripped(
271
            f"""\
272
/**
273
 * Compare two byte spans for equal content.
274
 */
275
private static Boolean byteSpansEqual(byte[] that, byte[] other) {{
276
{I}return that.equals(other);
277
}}"""
278
        ),
279
        Stripped(
280
            f"""\
281
private static class Pair<A, B> {{
282
{I}private final A first;
283
{I}private final B second;
284
{I}
285
{I}public Pair(A first, B second) {{
286
{II}this.first = first;
287
{II}this.second = second;
288
{I}}}
289
{I}
290
{I}public A getFirst() {{
291
{II}return first;
292
{I}}}
293
{I}
294
{I}public B getSecond() {{
295
{II}return second;
296
{I}}}
297
}}"""
298
        ),
299
        Stripped(
300
            f"""\
301
// Java 8 doesn't provide a zip operation out of the box, so we have to ship our own.
302
// Adapted from: https://stackoverflow.com/a/23529010
303
private static <A, B> Stream<Pair<A, B>> zip(
304
{I}Stream<? extends A> a,
305
{I}Stream<? extends B> b) {{
306
{I}Spliterator<? extends A> aSplit = Objects.requireNonNull(a).spliterator();
307
{I}Spliterator<? extends B> bSplit = Objects.requireNonNull(b).spliterator();
308
{I}
309
{I}int characteristics = aSplit.characteristics() & bSplit.characteristics() &
310
{II}~(Spliterator.DISTINCT | Spliterator.SORTED);
311
{I}
312
{I}long zipSize = ((characteristics & Spliterator.SIZED) != 0)
313
{II}? Math.min(aSplit.getExactSizeIfKnown(), bSplit.getExactSizeIfKnown())
314
{II}: -1;
315
{I}
316
{I}Iterator<A> aIter = Spliterators.iterator(aSplit);
317
{I}Iterator<B> bIter = Spliterators.iterator(bSplit);
318
{I}Iterator<Pair<A, B>> cIter = new Iterator<Pair<A, B>>() {{
319
{II}@Override
320
{II}public boolean hasNext() {{
321
{III}return aIter.hasNext() && bIter.hasNext();
322
{II}}}
323
{II}
324
{II}@Override
325
{II}public Pair<A, B> next() {{
326
{III}return new Pair<>(aIter.next(), bIter.next());
327
{II}}}
328
{I}}};
329
{I}
330
{I}Spliterator<Pair<A, B>> split = Spliterators.spliterator(cIter, zipSize, characteristics);
331
{I}return StreamSupport.stream(split, false);
332
}}"""
333
        ),
334
    ]  # type: List[Stripped]
335

336
    for concrete_cls in symbol_table.concrete_classes:
4✔
337
        blocks.append(_generate_shallow_equals(cls=concrete_cls))
4✔
338

339
    for concrete_cls in symbol_table.concrete_classes:
4✔
340
        blocks.append(_generate_deep_equals(cls=concrete_cls))
4✔
341

342
    for concrete_cls in symbol_table.concrete_classes:
4✔
343
        cls_name = java_naming.class_name(concrete_cls.name)
4✔
344

345
        blocks.append(
4✔
346
            Stripped(
347
                f"""\
348
@Test
349
public void test{cls_name}ShallowCopy() throws IOException {{
350
{I}final {cls_name} instance = CommonJsonization.loadMaximal{cls_name}();
351
{I}final {cls_name} instanceCopy = Copying.shallow(instance);
352

353
{I}assertTrue(
354
{II}{cls_name}ShallowEquals(instance, instanceCopy),
355
{II}{java_common.string_literal(cls_name)});
356
}} // public void test{cls_name}ShallowCopy"""
357
            )
358
        )
359

360
        blocks.append(
4✔
361
            Stripped(
362
                f"""\
363
@Test
364
public void test{cls_name}DeepCopy() throws IOException {{
365
{I}final {cls_name} instance = CommonJsonization.loadMaximal{cls_name}();
366
{I}final {cls_name} instanceCopy = Copying.deep(instance);
367

368
{I}assertTrue(
369
{II}{cls_name}DeepEquals(instance, instanceCopy),
370
{II}{java_common.string_literal(cls_name)});
371
}} // public void test{cls_name}DeepCopy"""
372
            )
373
        )
374

375
    blocks_joined = "\n\n".join(blocks)
4✔
376

377
    writer = io.StringIO()
4✔
378
    writer.write(
4✔
379
        f"""\
380
{java_common.WARNING}
381

382
package {package}.tests;
383

384
import static org.junit.jupiter.api.Assertions.assertTrue;
385

386
import {package}.copying.Copying;
387
import {package}.types.impl.*;
388
import {package}.types.model.*;
389
import {package}.types.model.IClass;
390
import {package}.visitation.AbstractTransformerWithContext;
391
import java.io.IOException;
392
import java.util.*;
393
import java.util.stream.Stream;
394
import java.util.stream.StreamSupport;
395
import org.junit.jupiter.api.Test;
396

397
public class TestCopying {{
398
{I}{indent_but_first_line(blocks_joined, I)}
399
}} // class TestCopying
400

401
// package {package}.tests
402

403
{java_common.WARNING}
404
"""
405
    )
406

407
    return [java_common.JavaFile("TestCopying.java", writer.getvalue())]
4✔
408

409

410
assert generate.__doc__ is not None
4✔
411
assert generate.__doc__.strip().startswith(__doc__.strip())
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc