Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 87 additions & 45 deletions .github/workflows/check-jacoco.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,103 @@ on:
branches: [ master ]
types: [ opened, edited, synchronize, reopened ]

permissions:
contents: read

# Cancel superseded runs on the same ref to save runner minutes.
concurrency:
group: jacoco-report-${{ github.ref }}
cancel-in-progress: true

env:
# hint: "group thresholds" are in format: 'overall*changed-files-average*per-changed-file'
# one report group per Scala version
# scala: [ 2.11, 2.12, 2.13 ] -- 2.13 produce problems | Support can wait until we will decide to move from 2.12
REPORT_GROUPS: |
- name: scala-2.11
paths:
- 'coverage/scala-2.11/jacoco.xml'
thresholds: '76*80*60'
- name: scala-2.12
paths:
- 'coverage/scala-2.12/jacoco.xml'
thresholds: '72*80*60'

jobs:
test:
runs-on: [ubuntu-latest]
strategy:
fail-fast: false
matrix:
# scala: [ 2.11, 2.12, 2.13 ] -- 2.13 produce problems | Support can wait until we will decide to move from 2.12
scala: [ 2.11, 2.12 ]
detect:
name: Detect Changed Files
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
scala_changed: ${{ steps.filter.outputs.scala }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Scala
uses: olafurpg/setup-scala@v10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
java-version: "adopt@1.8"
- name: Build and run tests
run: mvn clean verify -Pcode-coverage,scala-${{ matrix.scala }} --no-transfer-progress

- name: Upload Jacoco report as workflow artifact
uses: actions/upload-artifact@v4
persist-credentials: false
fetch-depth: 0
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d
id: filter
with:
name: PR jacoco report xml ${{ matrix.scala }}
path: target/site/jacoco/jacoco.xml
token: ""
filters: |
scala:
- '**/*.scala'
- '**/*.java'

- name: Checkout master branch into separate folder
uses: actions/checkout@v4
jacoco-report:
name: JaCoCo Report
needs: detect
if: needs.detect.outputs.scala_changed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: master
path: master_code
- name: Build and run tests (master branch)
working-directory: master_code
run: mvn clean verify -Pcode-coverage,scala-${{ matrix.scala }} --no-transfer-progress
persist-credentials: false
fetch-depth: 0

- name: Upload Jacoco report as workflow artifact
uses: actions/upload-artifact@v4
- name: Setup Scala
uses: olafurpg/setup-scala@32ffa16635ff8f19cc21ea253a987f0fdf29844c
with:
name: Master jacoco report xml ${{ matrix.scala }}
path: master_code/target/site/jacoco/jacoco.xml
java-version: "adopt@1.8"

- name: Build and run tests with coverage (Scala 2.11)
run: |
mvn clean verify -Pcode-coverage,scala-2.11 --no-transfer-progress
mkdir -p coverage/scala-2.11
cp target/jacoco.xml coverage/scala-2.11/jacoco.xml

- name: Build and run tests with coverage (Scala 2.12)
run: |
mvn clean verify -Pcode-coverage,scala-2.12 --no-transfer-progress
mkdir -p coverage/scala-2.12
cp target/jacoco.xml coverage/scala-2.12/jacoco.xml

- uses: actions/setup-python@v5
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: '3.12'
python-version: '3.14'

- name: Add JaCoCo Report in PR comments
id: jacoco
uses: MoranaApps/jacoco-report@v1.3.0
uses: MoranaApps/jacoco-report@562ba603c5b95021dbee26e89ae0f834f2dd8b9e
with:
token: '${{ secrets.GITHUB_TOKEN }}'
paths: |
**/target/site/jacoco/jacoco.xml
exclude-paths: |
master_code/**
baseline-paths: |
master_code/**/target/site/jacoco/jacoco.xml
title: JaCoCo code coverage report - Scala ${{ matrix.scala }}
sensitivity: "detail"
comment-mode: 'single'
min-coverage-overall: 75.0
min-coverage-changed-files: 80.0
token: ${{ secrets.GITHUB_TOKEN }}
global-thresholds: '74*80'
global-overall-scope: 'groups-only'
report-thresholds-default: '72*80*60'
skip-unchanged: 'true'
evaluate-unchanged: 'false'
report-groups: ${{ env.REPORT_GROUPS }}

noop:
name: No Operation
needs: detect
if: needs.detect.outputs.scala_changed != 'true'
runs-on: ubuntu-latest
steps:
- run: echo "No changes in Scala/Java related files — passing."
155 changes: 155 additions & 0 deletions jmf-rules.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#
# Copyright 2019 ABSA Group Limited
#
# Licensed 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.
#

# jacoco-method-filter — Rules (Scala / Maven) — za.co.absa.commons
# [jmf:2.1.0]
#
# Syntax reference, pitfalls, examples, and workflows: https://github.com/MoranaApps/jacoco-method-filter/blob/main/docs/rules-reference.md
#
# ─────────────────────────────────────────────────────────────────────────────
# HOW TO USE
# ─────────────────────────────────────────────────────────────────────────────
#
# 1) Review the GLOBAL RULES below — they cover compiler-generated boilerplate.
# 2) Add project-specific patterns in the PROJECT RULES section.
# 3) Keep rules narrow; add id: labels so logs are readable.
# Every rule must have an id:<label>. Unlabelled rules emit a [warn] at load
# time. CLI users: pass --strict to enforce id: as a hard CI requirement.
# (sbt/Maven plugins do not yet expose --strict as a configuration key.)
# 4) Use --verify mode to confirm rules match before committing.
#
# ─────────────────────────────────────────────────────────────────────────────
# GLOBAL RULES
# ─────────────────────────────────────────────────────────────────────────────
# Global rules match ALL packages. Verify they do not suppress real logic.
# See docs/rules-reference.md#global-rule-safety-warning for details.

# Scala case class helpers
*#canEqual(*) id:case-canequal
*#equals(*) id:case-equals
*#apply(*) id:case-apply
*#unapply(*) id:case-unapply
*#hashCode(*) id:case-hashcode
*#copy(*) id:case-copy
# Subsumed by gen-defaults below; kept for clarity. Safe to remove if gen-defaults is enabled.
*#copy$default$*(*) id:case-copy-defaults
*#productElement() id:case-prod-element
*#productArity() id:case-prod-arity
*#productPrefix() id:case-prod-prefix
*#productIterator() id:case-prod-iterator
*#tupled() id:case-tupled
*#curried() id:case-curried
*#toString() id:case-tostring
# Scala 2.13+ case class boilerplate — safe to enable; no-op in 2.11/2.12
# Note: unmatched
#*#productElementName(*) id:case-prod-element-name
#*#productElementNames() id:case-prod-element-names
# WARNING: may collide with domain methods named `name` — rescue with + include rule if needed
# Note: unmatched
#*#name() id:case-name
# WARNING: may collide with domain methods named `groups` — rescue with + include rule if needed
# Note: unmatched
#*#groups() id:case-groups
# WARNING: may collide with domain methods named `optionalAttributes` — rescue with + include rule if needed
# Note: unmatched
#*#optionalAttributes() id:case-optionalAttributes

# Companion objects, constructors, and static definitions
*$#<init>(*) id:gen-ctor
*$#<clinit>() id:gen-clinit

# Companion objects and defaults
*$*#apply(*) id:comp-apply
*$*#unapply(*) id:comp-unapply
*$*#toString(*) id:comp-tostring
*$*#readResolve(*) id:comp-readresolve

# Java serialization hook — compiler-generated on the case class itself (not the companion)
# Body is a single expression returning a serialization proxy; no project logic.
# WARNING: if a class overrides writeReplace with custom logic, rescue with + include rule.
# Note: unmatched
#*#writeReplace(*) id:case-writereplace

# anonymous class created by a macro expansion
# Note: unmatched
#*$macro$*#$anonfun$inst$macro$* id:macro-inst
#*$macro$*#inst$macro$* id:macro-inst

# lambda
*#* synthetic name-contains:$anonfun$ id:scala-anonfun

# Lazy val compute — compiler-generated synchronization wrapper
# Scala compiles `lazy val foo = expr` into an accessor + a `foo$lzycompute()` method.
# The $lzycompute method body IS the lazy initializer (expr). If the initializer contains
# real business logic, filtering this method hides it. Only enable when the lazy val is a
# trivial constant, a boundary call not unit-testable, or an already-tested computation.
# Rescue any lazy vals with real logic via + include rules.
# DISABLED — enable with + include rules to rescue lazy vals whose initializer has real logic
# *#*$lzycompute(*) id:scala-lzycompute

# Lambda serialization helper — compiler-generated private static method
# Emitted by scalac/javac to support SerializedLambda deserialization.
# NOT marked ACC_SYNTHETIC (unlike $anonfun$*), so the synthetic flag does not catch it.
# Body contains only a series of instanceof/handle comparisons — no project logic.
*#$deserializeLambda$(*) id:scala-deser-lambda

# Value class companion extension bridges — compiler-generated identity operations
# When a value class (AnyVal) is used in a boxed context, the Scala compiler emits
# these *$extension methods on the companion object. Each is a single-expression
# delegate to the unboxed static method; no project logic is present.
*#hashCode$extension(*) id:valclass-hashcode-ext
*#equals$extension(*) id:valclass-equals-ext
*#toString$extension(*) id:valclass-tostring-ext
*#canEqual$extension(*) id:valclass-canequal-ext
*#productIterator$extension(*) id:valclass-proditer-ext
*#productElement$extension(*) id:valclass-prodelem-ext
*#productArity$extension(*) id:valclass-prodarity-ext
*#productPrefix$extension(*) id:valclass-prodprefix-ext
*#copy$extension(*) id:valclass-copy-ext
*#copy$default$*$extension(*) id:valclass-copydef-ext

# Function1 trait mixin delegates — single-call forwarders to scala.Function1
# These arise when a class mixes in Function1. Each is a one-liner forwarding to
# the trait default. Rarely contain project logic.
# CAUTION: if your code defines andThen/compose with custom logic in a non-Function1
# class, rescue those methods with + include rules.
*#andThen(*) id:fn1-andthen
*#compose(*) id:fn1-compose

# Default parameter accessors — compiler-generated constant-returning methods
# The Scala compiler emits a $default$N method for every parameter with a default
# value. Each method body returns the constant default expression; no logic.
# This rule supersedes `case-copy-defaults` above; both are safe to keep active.
*#*$default$*(*) id:gen-defaults

# ─────────────────────────────────────────────────────────────────────────────
# INCLUDE RULES (rescue methods from broad exclusions above)
# ─────────────────────────────────────────────────────────────────────────────
# Prefix a rule with "+" to rescue a real domain method that collides with a broad
# GLOBAL rule (e.g. a hand-written apply/equals/copy that carries business logic).
# Include always wins over exclude. Add rescues here as they are discovered via
# `mvn compile jacoco-method-filter:verify`.
#
# Example:
# +*SomeDomainType$#apply(*) id:keep-somedomain-factory

# ─────────────────────────────────────────────────────────────────────────────
# PROJECT RULES
# ─────────────────────────────────────────────────────────────────────────────
# Project-specific exclusions for methods where a unit test adds no value
# (deprecated single-call delegates, trivial factory wrappers, implicit
# conversions, trivial accessors, etc.). Populate after reviewing the first
# coverage report. Keep each rule narrow and always add an id: label.
Loading