diff --git a/.github/scripts/udpate-spring-versions.py b/.github/scripts/udpate-spring-versions.py new file mode 100644 index 0000000..4c7537c --- /dev/null +++ b/.github/scripts/udpate-spring-versions.py @@ -0,0 +1,71 @@ +import json +import re +from collections import defaultdict +from packaging.version import Version +from xml.etree import ElementTree as ET +import urllib.request + +# Configuration +managed_minors = {"3.0", "3.1", "3.2", "3.3", "3.4"} +boot_java_compatibility = { + "3.0": ["17"], # Java 21 not supported + "3.1": ["17", "21"], + "3.2": ["17", "21"], + "3.3": ["17", "21"], + "3.4": ["17", "21"] +} +output_path = ".github/spring-versions.json" + +# Step 1: Fetch Spring Boot versions +metadata_url = "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter/maven-metadata.xml" +with urllib.request.urlopen(metadata_url) as response: + xml_data = response.read() + +root = ET.fromstring(xml_data) +versions = [v.text for v in root.findall(".//version")] + +boot_versions = defaultdict(list) +for v in versions: + if not re.match(r"^\d+\.\d+\.\d+$", v): + continue + minor = ".".join(v.split(".")[:2]) + if minor in managed_minors: + boot_versions[minor].append(Version(v)) + +latest_boot_versions = {minor: str(max(vlist)) for minor, vlist in boot_versions.items()} + +# Step 2: Resolve Spring Framework version +def get_spring_framework_version(boot_version: str) -> str: + pom_url = f"https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter/{boot_version}/spring-boot-starter-{boot_version}.pom" + try: + with urllib.request.urlopen(pom_url) as response: + pom_data = response.read() + pom_root = ET.fromstring(pom_data) + ns = {'m': 'http://maven.apache.org/POM/4.0.0'} + for dep in pom_root.findall(".//m:dependency", ns): + gid = dep.find("m:groupId", ns) + aid = dep.find("m:artifactId", ns) + ver = dep.find("m:version", ns) + if gid is not None and aid is not None and ver is not None: + if gid.text == "org.springframework" and aid.text == "spring-core": + return ver.text + except Exception as e: + print(f"Warning: Failed to fetch framework version for {boot_version}: {e}") + return "unknown" + +# Step 3: Build matrix +matrix_entries = [] +for minor, boot_version in sorted(latest_boot_versions.items()): + framework_version = get_spring_framework_version(boot_version) + for java_version in boot_java_compatibility.get(minor, []): + matrix_entries.append({ + "boot": boot_version, + "framework": framework_version, + "java": str(java_version) + }) + +# Step 4: Save result +with open(output_path, "w") as f: + json.dump({"matrix": matrix_entries}, f, indent=2) + +print(f"Updated {output_path} with {len(matrix_entries)} matrix entries.") diff --git a/.github/spring-versions.json b/.github/spring-versions.json new file mode 100644 index 0000000..156adae --- /dev/null +++ b/.github/spring-versions.json @@ -0,0 +1,49 @@ +{ + "matrix": [ + { + "boot": "3.0.13", + "framework": "6.0.14", + "java": "17" + }, + { + "boot": "3.1.11", + "framework": "6.0.20", + "java": "17" + }, + { + "boot": "3.1.12", + "framework": "6.0.21", + "java": "21" + }, + { + "boot": "3.2.12", + "framework": "6.1.15", + "java": "17" + }, + { + "boot": "3.2.12", + "framework": "6.1.15", + "java": "21" + }, + { + "boot": "3.3.12", + "framework": "6.1.20", + "java": "17" + }, + { + "boot": "3.3.12", + "framework": "6.1.20", + "java": "21" + }, + { + "boot": "3.4.6", + "framework": "6.2.7", + "java": "17" + }, + { + "boot": "3.4.6", + "framework": "6.2.7", + "java": "21" + } + ] +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91aea9d..256623d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,12 +7,56 @@ on: workflow_dispatch: jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + name: Generate version matrix + steps: + - uses: actions/checkout@v4 + - name: Read matrix from JSON + id: set-matrix + run: | + MATRIX=$(jq -c '.matrix' .github/spring-versions.json) + echo "matrix={\"include\":$MATRIX}" >> $GITHUB_OUTPUT + + regression-tests: + needs: generate-matrix + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + name: Test Spring Boot ${{ matrix.boot }} / Java ${{ matrix.java }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ matrix.java }} + + - name: Run tests and generate reports + run: ./gradlew testAndReport -PspringBootVersion=${{ matrix.boot }} -PspringFrameworkVersion=${{ matrix.framework }} + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + if: always() + with: + name: report-java-${{ matrix.java }}-spring-boot-${{ matrix.boot }} + path: build/reports/** + retention-days: 5 + test: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: java: [ '17', '21' ] - name: Test with Java ${{ matrix.Java }} + name: Test Spring Boot latest / Java ${{ matrix.java }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -32,12 +76,12 @@ jobs: uses: actions/upload-artifact@v4 if: always() with: - name: report-java-${{ matrix.Java }} + name: report-java-${{ matrix.java }}-spring-boot-latest path: build/reports/** retention-days: 5 - name: Run Sonar analysis - if: matrix.Java == '17' + if: matrix.java == '17' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} @@ -47,9 +91,11 @@ jobs: runs-on: ubuntu-latest needs: [test] steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - name: Setup Java + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/update-spring-versions.yml b/.github/workflows/update-spring-versions.yml new file mode 100644 index 0000000..8f5b785 --- /dev/null +++ b/.github/workflows/update-spring-versions.yml @@ -0,0 +1,38 @@ +name: Update Spring Versions Matrix + +on: + schedule: + - cron: '0 3 1 * *' # Monthly on the 1st at 03:00 UTC + workflow_dispatch: # Allow manual trigger as well + +jobs: + update-matrix: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install Python dependencies + run: pip install packaging + + - name: Run update script + run: python .github/scripts/update-spring-versions.py + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + commit-message: "chore: update Spring Boot/Framework testing matrix" + title: "Update Spring Versions Matrix" + body: | + This PR updates the `spring-versions.json` matrix with the latest patch versions of Spring Boot and their corresponding Spring Framework versions for Java 17 and 21. + branch: update/spring-versions-matrix + labels: | + dependencies + spring + author: github-actions[bot] diff --git a/build.gradle b/build.gradle index 25bc300..f54991a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,10 @@ buildscript { ext { - springBootVersion = '3.5.0' - springVersion = '6.2.7' + springBootVersion = project.hasProperty('springBootVersion') ? project.springBootVersion : '3.5.0' + springVersion = project.hasProperty('springFrameworkVersion') ? project.springFrameworkVersion : '6.2.7' jacksonVersion = '2.19.0' retrofitVersion = '2.11.0' + mockitoVersion = '5.2.+' // At least Mockito 5.2.0 is required to Mock final classes } dependencies { classpath 'org.owasp:dependency-check-gradle' @@ -79,7 +80,7 @@ dependencies { compileOnly group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '4.12.0' // https://mvnrepository.com/artifact/de.siegmar/logback-gelf - compileOnly group: 'de.siegmar', name: 'logback-gelf', version: '6.1.1' + compileOnly group: 'de.siegmar', name: 'logback-gelf', version: '5.0.+' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: "${springBootVersion}" @@ -99,6 +100,12 @@ dependencies { // https://mvnrepository.com/artifact/ee.bitweb/spring-test-core testImplementation group: 'ee.bitweb', name: 'spring-test-core', version: '2.+' + // https://mvnrepository.com/artifact/org.mockito/mockito-core + testImplementation group: 'org.mockito', name: 'mockito-core', version: "${mockitoVersion}" + + // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter + testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: "${mockitoVersion}" + // https://mvnrepository.com/artifact/org.mock-server/mockserver-netty testImplementation ("org.mock-server:mockserver-netty:5.15.0") { exclude group: 'junit', module: 'junit'