Skip to content

Commit

Permalink
Merge branch 'develop' into chore/remove-course-management-lti-modules
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-lippert committed Jan 21, 2025
2 parents 8ed940d + 1543e3e commit d75573f
Show file tree
Hide file tree
Showing 212 changed files with 2,719 additions and 566 deletions.
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Prerequisites:
- [ ] Test 2

### Test Coverage
<!-- Please add the test coverages for all changed files modified in this PR here. You can use `supporting_script/generate_code_cov_table/generate_code_cov_table.py` to automatically generate the coverage table from the corresponding artefacts of your branch (follow the ReadMe for setup details). -->
<!-- Please add the test coverages for all changed files modified in this PR here. You can use `supporting_script/code-coverage/generate_code_cov_table/generate_code_cov_table.py` to automatically generate the coverage table from the corresponding artefacts of your branch (follow the ReadMe for setup details). -->
<!-- Alternatively you can execute the tests locally (see build.gradle and package.json) or look into the corresponding artefacts. -->
<!-- The line coverage must be above 90% for changes files, and you must use extensive and useful assertions for server tests and expect statements for client tests. -->
<!-- Note: Confirm in the last column that you have implemented extensive assertions for server tests and expect statements for client tests. -->
Expand Down
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ updates:

# Check for version updates for Python dependencies (coverage)
- package-ecosystem: "pip"
directory: "/supporting_scripts/generate_code_cov_table"
directory: "/supporting_scripts/code-coverage/generate_code_cov_table"
schedule:
interval: "weekly"
reviewers:
Expand Down
36 changes: 33 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,32 @@ jobs:
${{ env.java }}
cache: 'gradle'
- name: Java Tests
run: set -o pipefail && ./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification | tee tests.log
run: |
set -o pipefail
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
CURRENT_BRANCH="${{ github.ref_name }}"
if [[ "$DEFAULT_BRANCH" != "$CURRENT_BRANCH" ]]; then
# Explicitly fetch as the clone action only clones the current branch
git fetch origin "$DEFAULT_BRANCH"
chmod +x ./supporting_scripts/get_changed_modules.sh
CHANGED_MODULES=$(./supporting_scripts/get_changed_modules.sh "origin/$DEFAULT_BRANCH")
# Restrict executed tests to changed modules if there is diff between this and the base branch
if [ -n "${CHANGED_MODULES}" ]; then
IFS=,
TEST_MODULE_TAGS=$(echo "-DincludeModules=${CHANGED_MODULES[*]}")
echo "Executing tests for modules: $CHANGED_MODULES"
./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification "$TEST_MODULE_TAGS" | tee tests.log
exit 0
fi
fi
echo "Executing all tests"
./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification | tee tests.log
- name: Print failed tests
if: failure()
run: grep "Test >.* FAILED\$" tests.log || echo "No failed tests."
Expand Down Expand Up @@ -92,8 +117,13 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Coverage Report Server Tests
path: build/reports/jacoco/test/html/

path: build/reports/jacoco/test/html
- name: Append Per-Module Coverage to Job Summary
if: success() || failure()
run: |
AGGREGATED_REPORT_FILE=./module_coverage_report.md
python3 ./supporting_scripts/code-coverage/per_module_cov_report/parse_module_coverage.py build/reports/jacoco $AGGREGATED_REPORT_FILE
cat $AGGREGATED_REPORT_FILE > $GITHUB_STEP_SUMMARY
server-tests-mysql:
needs: [ server-tests ]
Expand Down
13 changes: 7 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ plugins {
id "io.spring.dependency-management" version "1.1.7"
id "nebula.lint" version "20.5.5"
id "org.liquibase.gradle" version "${liquibase_plugin_version}"
id "org.owasp.dependencycheck" version "12.0.0"
id "org.owasp.dependencycheck" version "12.0.1"
id "org.springframework.boot" version "${spring_boot_version}"
}

Expand Down Expand Up @@ -205,7 +205,7 @@ dependencies {
implementation "org.apache.sshd:sshd-sftp:${sshd_version}"

// https://mvnrepository.com/artifact/net.sourceforge.plantuml/plantuml
implementation "net.sourceforge.plantuml:plantuml:1.2024.8"
implementation "net.sourceforge.plantuml:plantuml:1.2025.0"
implementation "me.xdrop:fuzzywuzzy:1.4.0"
implementation("org.yaml:snakeyaml") {
version {
Expand Down Expand Up @@ -410,7 +410,7 @@ dependencies {
testImplementation "org.springframework.boot:spring-boot-starter-test:${spring_boot_version}"
testImplementation "org.springframework.security:spring-security-test:${spring_security_version}"
testImplementation "org.springframework.boot:spring-boot-test:${spring_boot_version}"
testImplementation "org.assertj:assertj-core:3.27.2"
testImplementation "org.assertj:assertj-core:3.27.3"
testImplementation "org.mockito:mockito-core:${mockito_version}"
testImplementation "org.mockito:mockito-junit-jupiter:${mockito_version}"

Expand Down Expand Up @@ -494,9 +494,10 @@ tasks.named("dependencyUpdates").configure {
// 2) Execute tests with coverage report: ./gradlew test jacocoTestReport -x webapp
// 2a) Execute tests without coverage report: ./gradlew test -x webapp
// 2b) Run a single test: ./gradlew test --tests ExamIntegrationTest -x webapp or ./gradlew test --tests ExamIntegrationTest.testGetExamScore -x webapp
// 2c) Execute tests with Postgres container: SPRING_PROFILES_INCLUDE=postgres ./gradlew test -x webapp
// 2d) Execute tests with MySQL container: SPRING_PROFILES_INCLUDE=mysql ./gradlew test -x webapp
// 3) Verify code coverage (after tests): ./gradlew jacocoTestCoverageVerification
// 2c) Run tests for modules: ./gradlew test -DincludeModules=athena,atlas -x webapp (executes all tests in directories ./src/main/test/.../athena and ./src/main/test/.../atlas) + ArchitectureTests
// 2d) Execute tests with Postgres container: SPRING_PROFILES_INCLUDE=postgres ./gradlew test -x webapp
// 2e) Execute tests with MySQL container: SPRING_PROFILES_INCLUDE=mysql ./gradlew test -x webapp
// 3) Verify code coverage (after tests): ./gradlew jacocoTestCoverageVerification -x webapp
// 4) Check Java code format: ./gradlew spotlessCheck -x webapp
// 5) Apply Java code formatter: ./gradlew spotlessApply -x webapp
// 6) Find dependency updates: ./gradlew dependencyUpdates -Drevision=release
Expand Down
14 changes: 14 additions & 0 deletions check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
set -o pipefail

chmod +x ./changed-modules.sh
CHANGED_MODULES=$(./changed-modules.sh)

if [ -n "${CHANGED_MODULES}" ]; then
# Always execute ArchitectureTests
CHANGED_MODULES+=("ArchitectureTest")

IFS=,
TEST_MODULE_TAGS=$(echo "-DtestTags=${CHANGED_MODULES[*]}")
fi

echo "./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification $TEST_MODULE_TAGS | tee tests.log"
2 changes: 2 additions & 0 deletions docker/test-server-multi-node-postgresql-localci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ services:
artemis-app-node-3:
condition: service_started
restart: always
command:
- rm -rf /var/log/nginx
volumes:
- ./nginx/artemis-upstream-multi-node.conf:/etc/nginx/includes/artemis-upstream.conf:ro
- ./nginx/artemis-ssh-upstream-multi-node.conf:/etc/nginx/includes/artemis-ssh-upstream.conf:ro
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ jplag_version=5.1.0
# NOTE: we cannot need to use the latest version 9.x or 10.x here as long as Stanford CoreNLP does not reference it
lucene_version=8.11.4
slf4j_version=2.0.16
sentry_version=7.20.0
sentry_version=7.20.1
liquibase_version=4.31.0
docker_java_version=3.4.1
logback_version=1.5.16
java_parser_version=3.26.2
byte_buddy_version=1.15.11
byte_buddy_version=1.16.1
netty_version=4.1.115.Final
mysql_version=9.1.0

Expand All @@ -48,7 +48,7 @@ testcontainer_version=1.20.4
gradle_node_plugin_version=7.1.0
apt_plugin_version=0.21
liquibase_plugin_version=3.0.1
modernizer_plugin_version=1.10.0
modernizer_plugin_version=1.11.0
spotless_plugin_version=7.0.2

org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en \
Expand Down
217 changes: 217 additions & 0 deletions gradle/jacoco.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
ext {
AggregatedCoverageThresholds = [
"INSTRUCTION": 0.895,
"CLASS": 56
];
// (Isolated) thresholds when executing each module on its own
ModuleCoverageThresholds = [
"assessment" : [
"INSTRUCTION": 0.779,
"CLASS": 8
],
"athena" : [
"INSTRUCTION": 0.856,
"CLASS": 2
],
"atlas" : [
"INSTRUCTION": 0.860,
"CLASS": 12
],
"buildagent" : [
"INSTRUCTION": 0.313,
"CLASS": 13
],
"communication": [
"INSTRUCTION": 0.890,
"CLASS": 7
],
"core" : [
"INSTRUCTION": 0.657,
"CLASS": 69
],
"exam" : [
"INSTRUCTION": 0.914,
"CLASS": 1
],
"exercise" : [
"INSTRUCTION": 0.649,
"CLASS": 9
],
"fileupload" : [
"INSTRUCTION": 0.906,
"CLASS": 0
],
"iris" : [
"INSTRUCTION": 0.795,
"CLASS": 17
],
"lecture" : [
"INSTRUCTION": 0.867,
"CLASS": 0
],
"lti" : [
"INSTRUCTION": 0.770,
"CLASS": 3
],
"modeling" : [
"INSTRUCTION": 0.891,
"CLASS": 2
],
"plagiarism" : [
"INSTRUCTION": 0.760,
"CLASS": 1
],
"programming" : [
"INSTRUCTION": 0.863,
"CLASS": 12
],
"quiz" : [
"INSTRUCTION": 0.784,
"CLASS": 6
],
"text" : [
"INSTRUCTION": 0.847,
"CLASS": 0
],
"tutorialgroup": [
"INSTRUCTION": 0.915,
"CLASS": 0
],
]
// If no explicit modules defined -> generate reports and validate for each module
reportedModules = includedModules.size() == 0
? ModuleCoverageThresholds.collect {element -> element.key}
: includedModules as ArrayList

// we want to ignore some generated files in the domain folders
ignoredDirectories = [
"**/$BasePath/**/domain/**/*_*",
"**/$BasePath/core/config/migration/entries/**",
"**/gradle-wrapper.jar/**"
]
}

jacoco {
toolVersion = "0.8.12"
}

jacocoTestReport {
// For the aggregated report
reports {
xml.required = true
xml.outputLocation = file("build/reports/jacoco/test/jacocoTestReport.xml")
html.required = true
html.outputLocation = file("build/reports/jacoco/test/html")
}

finalizedBy reportedModules
.collect { module -> registerJacocoReportTask(module as String, jacocoTestReport) }
.findAll { task -> task != null}
}

jacocoTestCoverageVerification {
// Only run full coverage when no specific modules set
enabled = reportedModules.size() == 0

def minInstructionCoveredRatio = AggregatedCoverageThresholds["INSTRUCTION"] as double
def maxNumberUncoveredClasses = AggregatedCoverageThresholds["CLASS"] as int
applyVerificationRule(jacocoTestCoverageVerification, minInstructionCoveredRatio, maxNumberUncoveredClasses)

finalizedBy reportedModules
.collect { module -> registerJacocoTestCoverageVerification(module as String, jacocoTestCoverageVerification) }
.findAll { task -> task != null}
}
check.dependsOn jacocoTestCoverageVerification

/**
* Registers a JacocoReport task based on the provided parameters.
*
* @param moduleName The module name to include in the report.
* @param rootTask The root JacocoReport root task.
* @return The configured JacocoReport task.
*/
private JacocoReport registerJacocoReportTask(String moduleName, JacocoReport rootTask) {
def taskName = "jacocoCoverageReport-$moduleName"

JacocoReport task = project.tasks.register(taskName, JacocoReport).get()
task.description = "Generates JaCoCo coverage report for $moduleName"

prepareJacocoReportTask(task, moduleName, rootTask)

task.reports {
xml.required = true
xml.outputLocation = file("build/reports/jacoco/$moduleName/jacocoTestReport.xml")
html.required = true
html.outputLocation = file("build/reports/jacoco/$moduleName/html")
}

return task
}

/**
* Registers a JacocoCoverageVerification task based on the provided parameters.
*
* @param moduleName The module name to validate rules for.
* @param rootTask The root JacocoCoverageVerification task.
* @return The configured JacocoCoverageVerification task.
*/
private JacocoCoverageVerification registerJacocoTestCoverageVerification(String moduleName, JacocoCoverageVerification rootTask) {
def taskName = "jacocoTestCoverageVerification-$moduleName"

def thresholds = ModuleCoverageThresholds[moduleName]
if (thresholds == null) {
println "No coverage thresholds defined for module '$moduleName'. Skipping verification for this module..."
return null
}
def minInstructionCoveredRatio = thresholds["INSTRUCTION"] as double
def maxNumberUncoveredClasses = thresholds["CLASS"] as int

JacocoCoverageVerification task = project.tasks.register(taskName, JacocoCoverageVerification).get()
task.description = "Validates JaCoCo coverage for vioalations for $moduleName"

prepareJacocoReportTask(task, moduleName, rootTask)
applyVerificationRule(task, minInstructionCoveredRatio, maxNumberUncoveredClasses)

return task
}

/**
* Prepares a Jacoco report task (report & verification) to match a specific module.
* @param task that is modified
* @param moduleName of the module.
* @param rootTask the JacocoReportBase root task
*/
private void prepareJacocoReportTask(JacocoReportBase task, String moduleName, JacocoReportBase rootTask) {
task.group = "Reporting"
task.executionData = project.fileTree("${project.layout.buildDirectory.get()}/jacoco") {
include "test.exec"
}

def modulePath = "$BasePath/$moduleName/**/*.class"
task.sourceDirectories.setFrom(project.files("src/main/java/$modulePath"))
task.classDirectories.setFrom(
files(rootTask.classDirectories.files.collect { classDir ->
project.fileTree(classDir) {
includes=[modulePath]
excludes=ignoredDirectories
}
})
)
}

private static void applyVerificationRule(JacocoCoverageVerification task, double minInstructionCoveredRatio, int maxNumberUncoveredClasses) {
task.violationRules {
rule {
limit {
counter = "INSTRUCTION"
value = "COVEREDRATIO"
minimum = minInstructionCoveredRatio
}
limit {
counter = "CLASS"
value = "MISSEDCOUNT"
maximum = maxNumberUncoveredClasses
}
}
}
}
Loading

0 comments on commit d75573f

Please sign in to comment.