diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1113756..ff8f8e7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -18,6 +18,7 @@ StatusBoard is a real-time monitoring platform for tracking service health statu - **Database**: AWS DynamoDB for persistence - **Testing**: ZIO Test framework - **Code Coverage**: Jacoco +- Compiler warnings treated as errors where configured; coverage ≥ 80% via JMF-enabled JaCoCo (excluding methods listed in `jmf-rules.txt`). ### Frontend (TypeScript) - **Framework**: Angular 19 @@ -166,6 +167,29 @@ The checker system uses a polymorphic design: - E2E tests: `npm run e2e` (Cypress via `cypress.json`) - Test coverage: `npm run test:coverage` +## Coverage Filtering (JMF) + +### When a unit test adds value — write one +- The method has any logic of its own. + +### When to add to `jmf-rules.txt` instead of writing a unit test +- The body is a single call with no own logic: it forwards to another overload, calls its non-deprecated replacement, returns a field, or wraps a constructor with no transformation. +- **Litmus test**: "Does this method have any logic of its own?" — No → add a JMF rule instead of a test. + +### Global rule collision check (CRITICAL) +- When adding any new method, check whether its name matches a pattern in the `# GLOBAL RULES` section of `jmf-rules.txt`. +- If a method name matches a global rule AND the method contains domain logic: immediately add an INCLUDE rescue rule (`+FQCN#method(*)`) in the `# INCLUDE RULES` section of `jmf-rules.txt`. +- High-risk method names (most common collisions): `apply()`, `toString()`, `equals()`, `copy()`, `name()`, `groups()`, `optionalAttributes()`. See the `# GLOBAL RULES` section of `jmf-rules.txt` for the full list. +- Rationale: broad global rules are designed for compiler-generated boilerplate and can silently suppress coverage for domain methods. INCLUDE rules rescue specific methods from broad exclusions. +- Example: if adding `def apply(id: String): Record`, add `+*Record$#apply(*) id:keep-record-factory` to the `# INCLUDE RULES` section to rescue it from the `*$*#apply(*)` global rule. + +### JMF drift check (review rule) +- When modifying a method that already appears in `jmf-rules.txt`, verify its body still qualifies for exclusion. +- If own logic has been added since the rule was created, remove the JMF rule and write a unit test instead. + +### Cannot add JMF rules for +- Methods with branching logic, error handling, or non-trivial transformations — write a unit test instead. + ## Configuration - Main config: `config. conf` (HOCON format) - Environment-specific configurations for different deployments @@ -279,4 +303,4 @@ npm run sync-version # Sync VERSION file to package.json ## Additional Resources - [Wiki - REST API](https://github.com/AbsaOSS/StatusBoard/wiki/REST-API) - [Wiki - Architecture](https://github.com/AbsaOSS/StatusBoard/wiki/Architecture) -- [Wiki - Supported Checkers](https://github.com/AbsaOSS/StatusBoard/wiki/Supported-checkers) \ No newline at end of file +- [Wiki - Supported Checkers](https://github.com/AbsaOSS/StatusBoard/wiki/Supported-checkers) diff --git a/.github/workflows/ci-check-jacoco.yml b/.github/workflows/ci-check-jacoco.yml index d503ec0..470ad70 100644 --- a/.github/workflows/ci-check-jacoco.yml +++ b/.github/workflows/ci-check-jacoco.yml @@ -1,22 +1,48 @@ -name: JaCoCo Report +name: CI Check JaCoCo code-coverage on: pull_request: branches: [ master ] - types: [ opened, edited, synchronize, reopened ] env: scalaLong: 2.13.11 scalaShort: "2.13" - coverage-overall: 80.0 - coverage-changed-files: 80.0 - coverage-per-changed-file: 0.0 - check-overall-coverages: true + REPORT_GROUPS: | + - name: statusboard + paths: + - '**/target/**/jacoco-report/jacoco.xml' + thresholds: '77*80*60' jobs: - jacoco-report: - name: JaCoCo Report + detect: + name: Detect Changed Files runs-on: ubuntu-latest + timeout-minutes: 2 + outputs: + scala_changed: ${{ steps.filter.outputs.scala }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + with: + persist-credentials: false + fetch-depth: 0 + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d + id: filter + with: + token: "" + filters: | + scala: + - '**/*.scala' + - '!project/**' + + build-test-and-measure: + name: Build, Test and Measure + needs: detect + if: needs.detect.outputs.scala_changed == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write services: dynamodb-local: image: "amazon/dynamodb-local:latest" @@ -51,16 +77,21 @@ jobs: with: python-version: '3.14' - - name: Publish JaCoCo Report in PR comments + - name: Check coverage thresholds and add reports in PR comments id: jacoco uses: MoranaApps/jacoco-report@69351d88d18f7697c416e1bc2020ed05606d8120 with: token: '${{ secrets.GITHUB_TOKEN }}' - paths: | - **/target/**/jacoco/report/jacoco.xml - sensitivity: "detail" - comment-mode: 'single' - min-coverage-overall: ${{ env.coverage-overall }} - min-coverage-changed-files: ${{ env.coverage-changed-files }} - min-coverage-per-changed-file: ${{ env.coverage-per-changed-file }} - skip-unchanged: false + global-thresholds: '77*80' + report-thresholds-default: '0*0*0' + 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 src/**/*.scala — passing." diff --git a/build.sbt b/build.sbt index 796b8c8..cd74b8f 100644 --- a/build.sbt +++ b/build.sbt @@ -55,7 +55,9 @@ lazy val root = (project in file(".")) Test / parallelExecution := false, (assembly / test) := {}, publish := {}, + jmfReportFile := Some(target.value / "jmf-report.json"), + jmfReportFormat := "json", ) .enablePlugins(AutomateHeaderPlugin) .enablePlugins(AssemblyPlugin) - .enablePlugins(FilteredJacocoAgentPlugin) + .enablePlugins(JacocoFilterPlugin) diff --git a/jmf-rules.txt b/jmf-rules.txt index 949a90e..e2a67f2 100644 --- a/jmf-rules.txt +++ b/jmf-rules.txt @@ -1,118 +1,25 @@ -# jacoco-method-filter — Default Rules & HowTo (Scala) +# jacoco-method-filter — Rules Template (Scala / sbt) # [jmf:1.0.0] # -# This file defines which methods should be annotated as *Generated so JaCoCo ignores them. -# One rule per line. +# Syntax reference, pitfalls, examples, and workflows: docs/rules-reference.md # # ───────────────────────────────────────────────────────────────────────────── -# HOW TO USE (quick) -# 1) Replace YOUR.PACKAGE.ROOT with your project’s package root (e.g., com.example.app). -# 2) Start with the CONSERVATIVE section only. -# 3) If clean, enable STANDARD. Use AGGRESSIVE only inside DTO/auto‑generated packages. -# 4) Keep rules narrow (by package), prefer flags (synthetic/bridge) for compiler artifacts, -# and add `id:` labels so logs are easy to read. -# +# HOW TO USE # ───────────────────────────────────────────────────────────────────────────── -# ALLOWED SYNTAX (cheat sheet) -# -# General form: -# #() [FLAGS and PREDICATES...] -# -# FQCN_glob (dot form; $ allowed for inner classes): -# Examples: *.model.*, com.example.*, * -# -# method_glob (glob on method name): -# Examples: copy | $anonfun$* | get* | *_$eq -# -# descriptor_glob (JVM descriptor in (args)ret). You may omit it entirely. -# • Omitting descriptor ⇒ treated as "(*)*" (any args, any return). -# • Short/empty forms "", "()", "(*)" normalize to "(*)*". -# Examples: -# (I)I # takes int, returns int -# (Ljava/lang/String;)V # takes String, returns void -# () or (*) or omitted # any args, any return -# -# FLAGS (optional) — space or comma separated: -# public | protected | private | synthetic | bridge | static | abstract -# -# PREDICATES (optional): -# ret: # match return type only (e.g., ret:V, ret:I, ret:Lcom/example/*;) -# id: # identifier shown in logs/reports -# name-contains: # method name must contain -# name-starts: # method name must start with -# name-ends: # method name must end with # -# Notes -# - Always use dot-form (com.example.Foo) for class names. -# - Comments (# …) and blank lines are ignored. +# 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: