#!/usr/bin/env bash # Compiles the deliberately broken sources under src/broken/ with javac and commits the compiler's own words: # 17 unmapped target property 18 ambiguous mapping methods 19 missing source property # 20 @MappingTarget on a record (compiles!) 21 no way to map one type to another # 22 @DecoratedWith + constructor 23 processor on the plain classpath (JDK 23+) # 24 Lombok before/after MapStruct, with and without lombok-mapstruct-binding # Each step also greps for the expected message and fails the script if it is not there, so a MapStruct # upgrade that rewords an error is noticed. Needs a JDK 25 and the jars from one `mvn test` (they come from ~/.m2). set -uo pipefail cd "$(dirname "$0")/.." mkdir -p output target/broken M2=${M2_REPO:-$HOME/.m2/repository} MS=${MAPSTRUCT_VERSION:-1.6.3} LOMBOK=${LOMBOK_VERSION:-1.18.48} SPRING=${SPRING_VERSION:-7.0.10} native() { if command -v cygpath >/dev/null 2>&1; then cygpath -m "$1"; else echo "$1"; fi; } case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) SEP=';' ;; *) SEP=':' ;; esac MSJ=$(native "$M2/org/mapstruct/mapstruct/$MS/mapstruct-$MS.jar") MSP=$(native "$M2/org/mapstruct/mapstruct-processor/$MS/mapstruct-processor-$MS.jar") LOM=$(native "$M2/org/projectlombok/lombok/$LOMBOK/lombok-$LOMBOK.jar") BIND=$(native "$M2/org/projectlombok/lombok-mapstruct-binding/0.2.0/lombok-mapstruct-binding-0.2.0.jar") SPR="$(native "$M2/org/springframework/spring-context/$SPRING/spring-context-$SPRING.jar")$SEP$(native "$M2/org/springframework/spring-beans/$SPRING/spring-beans-$SPRING.jar")$SEP$(native "$M2/org/springframework/spring-core/$SPRING/spring-core-$SPRING.jar")" fail=0 # capture <expected text or ''> <display command> -- <real command...> capture() { local file=$1 title=$2 expect=$3 shown=$4; shift 5 local out out=$("$@" 2>&1 | sed 's/\r$//; s#\\#/#g') { echo "# $title" echo echo "\$ $shown" printf '%s\n' "$out" } > "output/$file" if [ -n "$expect" ] && ! grep -qF -- "$expect" "output/$file"; then echo "MISSING in output/$file: $expect" >&2 fail=1 fi echo "wrote output/$file" } javac_case() { # <case dir> -> real javac invocation with the processor on -processorpath local c=$1; rm -rf "target/broken/$c"; mkdir -p "target/broken/$c" echo "javac -d target/broken/$c -cp \$MAPSTRUCT_JAR -processorpath \$MAPSTRUCT_PROCESSOR_JAR src/broken/$c/*.java" } for spec in \ "17-error-unmapped-target.txt|unmapped-target|Unmapped target property: \"note\"|A record target component nobody mapped (unmappedTargetPolicy = ERROR)" \ "18-error-ambiguous-mapping-methods.txt|ambiguous|Ambiguous mapping methods found for mapping property|Two methods can produce the same property" \ "19-error-missing-source-property.txt|missing-source|No property named \"nmae\" exists in source parameter(s)|A typo in @Mapping(source = ...)" \ "21-error-cannot-map-type.txt|cannot-map|Can't map property|No way to turn one type into another"; do IFS='|' read -r file case expect title <<<"$spec" shown=$(javac_case "$case") capture "$file" "$title" "$expect" "$shown" -- \ javac -d "target/broken/$case" -cp "$MSJ" -processorpath "$MSP" src/broken/$case/*.java done # 20: @MappingTarget on a record is NOT a compile error: read what was generated c=update-record; rm -rf "target/broken/$c"; mkdir -p "target/broken/$c" { echo "# @MappingTarget on a record compiles, with unmappedTargetPolicy = ERROR, and generates this" echo echo "\$ javac -d target/broken/$c -s target/broken/$c -cp \$MAPSTRUCT_JAR -processorpath \$MAPSTRUCT_PROCESSOR_JAR src/broken/$c/*.java" javac -d "target/broken/$c" -s "target/broken/$c" -cp "$MSJ" -processorpath "$MSP" src/broken/$c/*.java 2>&1 | sed 's/ $//' echo "(exit status: ${PIPESTATUS[0]})" echo echo "\$ cat target/broken/$c/BrokenMapperImpl.java (header and imports trimmed)" sed -n '/^public class/,$p' "target/broken/$c/BrokenMapperImpl.java" | sed 's/ $//' } > output/20-update-method-on-a-record-compiles-to-a-no-op.txt echo "wrote output/20-update-method-on-a-record-compiles-to-a-no-op.txt" grep -q 'public void update' output/20-update-method-on-a-record-compiles-to-a-no-op.txt || { echo "MISSING update in 20" >&2; fail=1; } # 22: the decorator needs the Spring annotations on the classpath c=decorator-constructor; rm -rf "target/broken/$c"; mkdir -p "target/broken/$c" capture 22-error-decorator-constructor.txt "@DecoratedWith with a constructor-injected delegate (componentModel = spring)" \ "constructor BrokenMapperDecorator in class BrokenMapperDecorator cannot be applied to given types" \ "javac -d target/broken/$c -cp \$MAPSTRUCT_JAR:\$SPRING_JARS -processorpath \$MAPSTRUCT_PROCESSOR_JAR src/broken/$c/*.java" -- \ javac -d "target/broken/$c" -cp "$MSJ$SEP$SPR" -processorpath "$MSP" src/broken/$c/*.java # 23: processor jar on the plain classpath, no -processorpath, no -proc:full c=processor-not-run; rm -rf "target/broken/$c"; mkdir -p "target/broken/$c" { echo "# The processor jar on the plain classpath: javac $(javac -version 2>&1 | awk '{print $2}') compiles without generating anything" echo echo "\$ javac -d target/broken/$c -cp \$MAPSTRUCT_JAR:\$MAPSTRUCT_PROCESSOR_JAR src/broken/$c/UsesMapper.java" javac -d "target/broken/$c" -cp "$MSJ$SEP$MSP" src/broken/$c/UsesMapper.java 2>&1 | sed 's/\r$//' echo "(exit status: ${PIPESTATUS[0]})" echo echo "\$ ls target/broken/$c" ls "target/broken/$c" | sed 's/\r$//' echo echo "\$ java -cp \$MAPSTRUCT_JAR:target/broken/$c UsesMapper" java -cp "$MSJ$SEP""target/broken/$c" UsesMapper 2>&1 | sed 's/\r$//' | head -4 echo rm -rf "target/broken/$c"; mkdir -p "target/broken/$c" echo "\$ javac -proc:full -d target/broken/$c -cp \$MAPSTRUCT_JAR:\$MAPSTRUCT_PROCESSOR_JAR src/broken/$c/UsesMapper.java" javac -proc:full -d "target/broken/$c" -cp "$MSJ$SEP$MSP" src/broken/$c/UsesMapper.java 2>&1 | sed 's/\r$//' echo "(exit status: ${PIPESTATUS[0]})" echo "\$ ls target/broken/$c" ls "target/broken/$c" | sed 's/\r$//' } > output/23-processor-on-the-plain-classpath.txt echo "wrote output/23-processor-on-the-plain-classpath.txt" grep -q 'UsesMapperImpl.class' output/23-processor-on-the-plain-classpath.txt || { echo "MISSING UsesMapperImpl in 23" >&2; fail=1; } # 24: Lombok and MapStruct ordering c=lombok run_lombok() { # <label> <processorpath> rm -rf "target/broken/$c"; mkdir -p "target/broken/$c" echo echo "--- $1 ---" echo "\$ javac -d target/broken/$c -cp \$MAPSTRUCT_JAR:\$LOMBOK_JAR -processorpath $3 src/broken/$c/LombokMapper.java" javac -d "target/broken/$c" -cp "$MSJ$SEP$LOM" -processorpath "$2" src/broken/$c/LombokMapper.java 2>&1 | sed 's/\r$//; s#\\#/#g' | grep -v '^WARNING:' echo "(exit status: ${PIPESTATUS[0]})" echo "LombokMapperImpl generated: $([ -f target/broken/$c/LombokMapperImpl.class ] && echo yes || echo no)" } { echo "# Lombok + MapStruct + a record target: what the processor path order and lombok-mapstruct-binding change" run_lombok "MapStruct first, Lombok second, no binding" "$MSP$SEP$LOM" '$MAPSTRUCT_PROCESSOR_JAR:$LOMBOK_JAR' run_lombok "Lombok first, MapStruct second, no binding" "$LOM$SEP$MSP" '$LOMBOK_JAR:$MAPSTRUCT_PROCESSOR_JAR' run_lombok "MapStruct first, then Lombok and lombok-mapstruct-binding" "$MSP$SEP$LOM$SEP$BIND" '$MAPSTRUCT_PROCESSOR_JAR:$LOMBOK_JAR:$BINDING_JAR' run_lombok "Lombok, binding, then MapStruct" "$LOM$SEP$BIND$SEP$MSP" '$LOMBOK_JAR:$BINDING_JAR:$MAPSTRUCT_PROCESSOR_JAR' } > output/24-lombok-processor-order.txt echo "wrote output/24-lombok-processor-order.txt" exit $fail