goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
@@ -0,0 +1,341 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# test_prepare_release.sh
#
# Tests the responsibilities of prepare-release.sh:
# - CHANGELOG transformation
# - README version updates
# - Release notes file creation
# - Output variables
# =============================================================================
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/prepare-release.sh"
cleanup() {
if [[ -n "${TMP:-}" && -d "$TMP" ]]; then
rm -rf "$TMP"
fi
return 0
}
trap cleanup EXIT
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
expect_contains() {
local haystack="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq "$needle" <<<"$haystack"; then
fail "$msg (missing: '$needle')"
fi
}
expect_file_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" "$file"; then
fail "$msg (missing: '$needle')"
fi
}
expect_file_matches() {
local file="$1"
local regex="$2"
local msg="$3"
if ! grep -Eq -- "$regex" "$file"; then
fail "$msg (regex mismatch: '$regex')"
fi
}
run_test() {
TMP="$(mktemp -d "${TMPDIR:-/tmp}/prepare-release-test.XXXXXX")"
mkdir -p "$TMP/scripts"
cp "$SCRIPT" "$TMP/scripts/prepare-release.sh"
chmod +x "$TMP/scripts/prepare-release.sh"
pushd "$TMP" >/dev/null || exit 1
}
finish_test() {
popd >/dev/null || exit 1
rm -rf "$TMP"
}
# -----------------------------------------------------------------------------
# TEST 1: Basic Release Flow
# -----------------------------------------------------------------------------
test_basic_flow() {
echo "Running test_basic_flow"
run_test
cat > CHANGELOG.md <<'EOF'
## Unreleased
- Added new Indicator ABC.
## 1.2.3 (2024-01-01)
- Older note
EOF
cat > README.md <<'EOF'
<!-- TA4J_VERSION_BLOCK:core:stable:begin -->
<dependency>
<artifactId>ta4j-core</artifactId>
<version>0.0.1</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:core:stable:end -->
<!-- TA4J_VERSION_BLOCK:core:snapshot:begin -->
<dependency>
<artifactId>ta4j-core</artifactId>
<version>0.0.2-SNAPSHOT</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:core:snapshot:end -->
<!-- TA4J_VERSION_BLOCK:examples:stable:begin -->
<dependency>
<artifactId>ta4j-examples</artifactId>
<version>0.0.1</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:examples:stable:end -->
<!-- TA4J_VERSION_BLOCK:examples:snapshot:begin -->
<dependency>
<artifactId>ta4j-examples</artifactId>
<version>0.0.2-SNAPSHOT</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:examples:snapshot:end -->
EOF
OUT="$(scripts/prepare-release.sh 1.3.0)"
expect_contains "$OUT" "release_version=1.3.0" "script should print release version"
expect_contains "$OUT" "release_notes_file=release/1.3.0.md" "script should print release notes file path"
expect_file_contains CHANGELOG.md "## Unreleased" "Unreleased should be preserved"
expect_file_contains CHANGELOG.md "- _No changes yet._" "Unreleased should be reset with placeholder"
expect_file_matches CHANGELOG.md "## 1\\.3\\.0 \\([0-9]{4}-[0-9]{2}-[0-9]{2}\\)" "new release header should exist"
expect_file_contains CHANGELOG.md "Added new Indicator ABC." "notes should move into release section"
expect_file_contains release/1.3.0.md "Added new Indicator ABC." "release notes file should include notes"
expect_file_matches release/1.3.0.md "^## 1\\.3\\.0 \\([0-9]{4}-[0-9]{2}-[0-9]{2}\\)" "release notes should have correct header"
expect_file_contains README.md "<version>1.3.0</version>" "README version should be updated"
expect_file_contains README.md "<version>1.3.1-SNAPSHOT</version>" "README snapshot version should be incremented and preserved"
finish_test
pass "test_basic_flow"
}
# -----------------------------------------------------------------------------
# TEST 2: Missing Unreleased Section
# -----------------------------------------------------------------------------
test_missing_unreleased() {
echo "Running test_missing_unreleased"
run_test
cat > CHANGELOG.md <<'EOF'
## 1.2.0 (2023-12-01)
- Old release
EOF
cat > README.md <<'EOF'
<!-- TA4J_VERSION_BLOCK:core:stable:begin -->
<version>0.0.1</version>
<!-- TA4J_VERSION_BLOCK:core:stable:end -->
<!-- TA4J_VERSION_BLOCK:core:snapshot:begin -->
<version>0.0.1-SNAPSHOT</version>
<!-- TA4J_VERSION_BLOCK:core:snapshot:end -->
<!-- TA4J_VERSION_BLOCK:examples:stable:begin -->
<version>0.0.1</version>
<!-- TA4J_VERSION_BLOCK:examples:stable:end -->
<!-- TA4J_VERSION_BLOCK:examples:snapshot:begin -->
<version>0.0.1-SNAPSHOT</version>
<!-- TA4J_VERSION_BLOCK:examples:snapshot:end -->
EOF
OUT="$(scripts/prepare-release.sh 1.3.0)"
expect_file_contains CHANGELOG.md "## Unreleased" "should create Unreleased"
expect_file_contains CHANGELOG.md "## 1.3.0" "should add release section"
finish_test
pass "test_missing_unreleased"
}
# -----------------------------------------------------------------------------
# TEST 3: Empty CHANGELOG
# -----------------------------------------------------------------------------
test_empty_changelog() {
echo "Running test_empty_changelog"
run_test
touch CHANGELOG.md
cat > README.md <<'EOF'
<!-- TA4J_VERSION_BLOCK:core:stable:begin -->
<version>0.0.1</version>
<!-- TA4J_VERSION_BLOCK:core:stable:end -->
<!-- TA4J_VERSION_BLOCK:core:snapshot:begin -->
<version>0.0.1-SNAPSHOT</version>
<!-- TA4J_VERSION_BLOCK:core:snapshot:end -->
<!-- TA4J_VERSION_BLOCK:examples:stable:begin -->
<version>0.0.1</version>
<!-- TA4J_VERSION_BLOCK:examples:stable:end -->
<!-- TA4J_VERSION_BLOCK:examples:snapshot:begin -->
<version>0.0.1-SNAPSHOT</version>
<!-- TA4J_VERSION_BLOCK:examples:snapshot:end -->
EOF
OUT="$(scripts/prepare-release.sh 1.0.0)"
expect_file_contains CHANGELOG.md "## Unreleased" "should add Unreleased"
expect_file_contains CHANGELOG.md "## 1.0.0" "should add release section"
expect_file_contains CHANGELOG.md "- _No changes yet._" "should add placeholder"
finish_test
pass "test_empty_changelog"
}
# -----------------------------------------------------------------------------
# TEST 4: Snapshot Version Increment (major.minor.patch)
# -----------------------------------------------------------------------------
test_snapshot_patch_increment() {
echo "Running test_snapshot_patch_increment"
run_test
cat > CHANGELOG.md <<'EOF'
## Unreleased
- Some change.
EOF
cat > README.md <<'EOF'
<!-- TA4J_VERSION_BLOCK:core:stable:begin -->
<dependency>
<artifactId>ta4j-core</artifactId>
<version>0.1</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:core:stable:end -->
<!-- TA4J_VERSION_BLOCK:core:snapshot:begin -->
<dependency>
<artifactId>ta4j-core</artifactId>
<version>0.2-SNAPSHOT</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:core:snapshot:end -->
<!-- TA4J_VERSION_BLOCK:examples:stable:begin -->
<dependency>
<artifactId>ta4j-examples</artifactId>
<version>0.1</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:examples:stable:end -->
<!-- TA4J_VERSION_BLOCK:examples:snapshot:begin -->
<dependency>
<artifactId>ta4j-examples</artifactId>
<version>0.2-SNAPSHOT</version>
</dependency>
<!-- TA4J_VERSION_BLOCK:examples:snapshot:end -->
EOF
scripts/prepare-release.sh 2.7.4 >/dev/null
expect_file_contains README.md "<version>2.7.4</version>" "README stable version should be updated"
expect_file_contains README.md "<version>2.7.5-SNAPSHOT</version>" "README snapshot version should increment patch and preserve suffix"
finish_test
pass "test_snapshot_patch_increment"
}
# -----------------------------------------------------------------------------
# TEST 5: Missing README Sentinel
# -----------------------------------------------------------------------------
test_missing_readme_sentinel() {
echo "Running test_missing_readme_sentinel"
run_test
cat > CHANGELOG.md <<'EOF'
## Unreleased
- Some change.
EOF
cat > README.md <<'EOF'
<!-- TA4J_VERSION_BLOCK:core:stable:begin -->
<version>0.1</version>
<!-- TA4J_VERSION_BLOCK:core:stable:end -->
EOF
if scripts/prepare-release.sh 1.0.0 >/dev/null 2>&1; then
fail "script should fail when README sentinels are missing"
fi
finish_test
pass "test_missing_readme_sentinel"
}
# -----------------------------------------------------------------------------
# TEST 6: Invalid Release Version
# -----------------------------------------------------------------------------
test_invalid_release_version() {
echo "Running test_invalid_release_version"
run_test
cat > CHANGELOG.md <<'EOF'
## Unreleased
- Some change.
EOF
cat > README.md <<'EOF'
<!-- TA4J_VERSION_BLOCK:core:stable:begin -->
<version>0.1</version>
<!-- TA4J_VERSION_BLOCK:core:stable:end -->
<!-- TA4J_VERSION_BLOCK:core:snapshot:begin -->
<version>0.1-SNAPSHOT</version>
<!-- TA4J_VERSION_BLOCK:core:snapshot:end -->
<!-- TA4J_VERSION_BLOCK:examples:stable:begin -->
<version>0.1</version>
<!-- TA4J_VERSION_BLOCK:examples:stable:end -->
<!-- TA4J_VERSION_BLOCK:examples:snapshot:begin -->
<version>0.1-SNAPSHOT</version>
<!-- TA4J_VERSION_BLOCK:examples:snapshot:end -->
EOF
if scripts/prepare-release.sh 1.3 >/dev/null 2>&1; then
fail "script should fail when release version is not major.minor.patch"
fi
expect_file_contains CHANGELOG.md "## Unreleased" "changelog should remain unchanged on version error"
expect_file_contains CHANGELOG.md "- Some change." "changelog entries should remain unchanged on version error"
finish_test
pass "test_invalid_release_version"
}
# -----------------------------------------------------------------------------
main() {
test_basic_flow
test_missing_unreleased
test_empty_changelog
test_snapshot_patch_increment
test_missing_readme_sentinel
test_invalid_release_version
echo "All tests passed."
}
main "$@"
@@ -0,0 +1,241 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
WORKFLOW="$ROOT/.github/workflows/prepare-release.yml"
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
expect_file_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" "$file"; then
fail "$msg (missing: '$needle')"
fi
}
line_of() {
local needle="$1"
local line
line="$(grep -nF -- "$needle" "$WORKFLOW" | head -n1 | cut -d: -f1)"
if [[ -z "$line" ]]; then
fail "missing workflow line: '$needle'"
fi
printf '%s\n' "$line"
}
workflow_section() {
local start="$1"
local end="$2"
awk -v start="$start" -v end="$end" '
index($0, start) { active = 1 }
active { print }
index($0, end) { active = 0 }
' "$WORKFLOW"
}
test_issue_permissions_declared() {
echo "Running test_issue_permissions_declared"
local permissions
permissions="$(ruby -e 'require "yaml"; workflow = YAML.load_file(ARGV[0]); puts workflow.fetch("permissions").fetch("issues")' "$WORKFLOW")"
if [[ "$permissions" != "write" ]]; then
fail "prepare-release workflow should request issues: write permission"
fi
pass "test_issue_permissions_declared"
}
test_issue_sync_uses_targeted_search() {
echo "Running test_issue_sync_uses_targeted_search"
expect_file_contains "$WORKFLOW" "search.issuesAndPullRequests" "issue sync should use targeted issue search"
expect_file_contains "$WORKFLOW" "is:issue in:body" "issue sync should search by marker in the body"
if grep -Fq "issues.listForRepo" "$WORKFLOW"; then
fail "issue sync should not paginate every repo issue"
fi
pass "test_issue_sync_uses_targeted_search"
}
test_deprecation_scan_uses_java_scanner() {
echo "Running test_deprecation_scan_uses_java_scanner"
expect_file_contains "$WORKFLOW" "exec:java" "deprecation scan should run through Maven exec"
expect_file_contains "$WORKFLOW" "-pl ta4j-examples -am -DskipTests install" \
"deprecation scan should install reactor dependencies before execution"
expect_file_contains "$WORKFLOW" "-pl ta4j-examples exec:java" \
"deprecation scan should run exec:java only on ta4j-examples"
expect_file_contains "$WORKFLOW" "ta4jexamples.doc.RemovalReadyDeprecationScanner" \
"deprecation scan should invoke the Java scanner"
if grep -Fq "scan-removal-ready-deprecations.py" "$WORKFLOW"; then
fail "deprecation scan should not depend on the removed Python scanner"
fi
pass "test_deprecation_scan_uses_java_scanner"
}
test_prepare_release_sets_up_jdk25_before_maven() {
echo "Running test_prepare_release_sets_up_jdk25_before_maven"
local setup_line
local read_version_line
local gate_line
setup_line="$(line_of "Set up JDK 25")"
read_version_line="$(line_of "Read current version from POM")"
gate_line="$(line_of "Gate release-ready deprecations")"
if (( setup_line >= read_version_line )); then
fail "prepare-release should configure JDK 25 before reading the Maven project version"
fi
if (( setup_line >= gate_line )); then
fail "prepare-release should configure JDK 25 before the deprecation release gate"
fi
expect_file_contains "$WORKFLOW" "uses: actions/setup-java@v5" \
"prepare-release should use the standard setup-java action"
expect_file_contains "$WORKFLOW" "distribution: temurin" \
"prepare-release should use the same Temurin distribution as other workflows"
expect_file_contains "$WORKFLOW" "java-version: 25" \
"prepare-release should satisfy the Maven enforcer Java version"
expect_file_contains "$WORKFLOW" "cache: maven" \
"prepare-release should enable Maven cache for Java setup"
pass "test_prepare_release_sets_up_jdk25_before_maven"
}
test_release_gate_runs_before_next_snapshot() {
echo "Running test_release_gate_runs_before_next_snapshot"
local gate_line
local next_snapshot_line
gate_line="$(line_of "Gate release-ready deprecations")"
next_snapshot_line="$(line_of "Set Maven version to next snapshot")"
if (( gate_line >= next_snapshot_line )); then
fail "release deprecation gate should run before next snapshot version is applied"
fi
expect_file_contains "$WORKFLOW" "--target-removal-version \${RELEASE_VERSION}" \
"release gate should scan against the release version"
expect_file_contains "$WORKFLOW" "--include-overdue --fail-on-due" \
"release gate should include overdue removals and fail on due removals"
expect_file_contains "$WORKFLOW" "release-ready-deprecations-\${{ steps.versions.outputs.release }}" \
"release gate report should be uploaded as its own artifact"
pass "test_release_gate_runs_before_next_snapshot"
}
test_next_snapshot_issue_sync_runs_after_snapshot_commit() {
echo "Running test_next_snapshot_issue_sync_runs_after_snapshot_commit"
local snapshot_commit_line
local scan_line
local sync_line
local push_line
snapshot_commit_line="$(line_of "Commit next snapshot version")"
scan_line="$(line_of "Scan removal-ready deprecations")"
sync_line="$(line_of "Create or update removal-ready deprecation issues")"
push_line="$(line_of "Push release branch")"
if (( scan_line <= snapshot_commit_line )); then
fail "next-snapshot deprecation scan should run after the next snapshot commit"
fi
if (( sync_line <= scan_line )); then
fail "deprecation issue sync should run after the next-snapshot scan"
fi
if (( sync_line >= push_line )); then
fail "deprecation issue sync should finish before the release branch is pushed"
fi
expect_file_contains "$WORKFLOW" "NEXT_REMOVAL_VERSION=\"\${NEXT_VERSION%-SNAPSHOT}\"" \
"next-snapshot scan should derive the planned removal target from nextVersion"
expect_file_contains "$WORKFLOW" "--target-removal-version \${NEXT_REMOVAL_VERSION}" \
"next-snapshot scan should not rely on a dry-run POM mutation"
expect_file_contains "$WORKFLOW" "--target-removal-version \${NEXT_REMOVAL_VERSION} --include-overdue" \
"next-snapshot scan should include overdue removal versions when release versions jump"
pass "test_next_snapshot_issue_sync_runs_after_snapshot_commit"
}
test_release_audit_includes_deprecation_counts() {
echo "Running test_release_audit_includes_deprecation_counts"
expect_file_contains "$WORKFLOW" "\"removalIssuePlans\"" \
"release audit JSON should include removal issue plan counts"
expect_file_contains "$WORKFLOW" "\"removalIssuesCreated\"" \
"release audit JSON should include created removal issue counts"
expect_file_contains "$WORKFLOW" "\"removalIssuesClosedStale\"" \
"release audit JSON should include stale issue closure counts"
expect_file_contains "$WORKFLOW" "removal-ready deprecation issue plans" \
"workflow summary should include removal-ready deprecation plan counts"
expect_file_contains "$WORKFLOW" "prepare-release-audit-\${{ github.run_id }}" \
"prepare-release audit artifact should still be uploaded"
pass "test_release_audit_includes_deprecation_counts"
}
test_issue_sync_reconciles_stale_managed_issues() {
echo "Running test_issue_sync_reconciles_stale_managed_issues"
expect_file_contains "$WORKFLOW" "ta4j:deprecation-removal version=\${removalVersion}" \
"issue sync should search managed deprecation issues by removal version"
expect_file_contains "$WORKFLOW" "closedStaleCount" \
"issue sync should report stale managed issue closures"
expect_file_contains "$WORKFLOW" "state_reason: \"completed\"" \
"stale managed issues should be closed as completed"
expect_file_contains "$WORKFLOW" "closed_stale_count" \
"closed stale issue count should be exposed as an output"
pass "test_issue_sync_reconciles_stale_managed_issues"
}
test_deprecation_issue_sync_does_not_add_metadata_to_cleanup_issues() {
echo "Running test_deprecation_issue_sync_does_not_add_metadata_to_cleanup_issues"
local sync_section
sync_section="$(workflow_section "Create or update removal-ready deprecation issues" "Upload removal-ready deprecation report")"
if grep -Fq "labels:" <<<"$sync_section"; then
fail "generated deprecation cleanup issues should not receive labels"
fi
if grep -Fq "assignees:" <<<"$sync_section"; then
fail "generated deprecation cleanup issues should not receive assignees"
fi
pass "test_deprecation_issue_sync_does_not_add_metadata_to_cleanup_issues"
}
test_deprecation_scans_run_during_dry_runs() {
echo "Running test_deprecation_scans_run_during_dry_runs"
local gate_section
local scan_section
local issue_section
gate_section="$(workflow_section "Gate release-ready deprecations" "Upload release-ready deprecation gate report")"
scan_section="$(workflow_section "Scan removal-ready deprecations" "Create or update removal-ready deprecation issues")"
issue_section="$(workflow_section "Create or update removal-ready deprecation issues" "Upload removal-ready deprecation report")"
if grep -Fq "if: steps.dry_run.outputs.dryRun != 'true'" <<<"$gate_section"; then
fail "release deprecation gate should run during dry-run"
fi
if grep -Fq "if: steps.dry_run.outputs.dryRun != 'true'" <<<"$scan_section"; then
fail "next-snapshot deprecation scan should run during dry-run"
fi
expect_file_contains "$WORKFLOW" "scan_status=\$scan_status" \
"release gate should expose scan status for delayed dry-run failure"
expect_file_contains "$WORKFLOW" "if: steps.dry_run.outputs.dryRun == 'true' && steps.deprecation_release_gate.outputs.scan_status != '0'" \
"dry-run should fail after report uploads when release-ready deprecations are found"
if ! grep -Fq "if: steps.dry_run.outputs.dryRun != 'true'" <<<"$issue_section"; then
fail "deprecation issue sync should skip dry-run mutations"
fi
pass "test_deprecation_scans_run_during_dry_runs"
}
test_issue_permissions_declared
test_issue_sync_uses_targeted_search
test_deprecation_scan_uses_java_scanner
test_prepare_release_sets_up_jdk25_before_maven
test_release_gate_runs_before_next_snapshot
test_next_snapshot_issue_sync_runs_after_snapshot_commit
test_release_audit_includes_deprecation_counts
test_issue_sync_reconciles_stale_managed_issues
test_deprecation_issue_sync_does_not_add_metadata_to_cleanup_issues
test_deprecation_scans_run_during_dry_runs
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
POM="$ROOT/pom.xml"
CORE_POM="$ROOT/ta4j-core/pom.xml"
EXAMPLES_POM="$ROOT/ta4j-examples/pom.xml"
WORKFLOW="$ROOT/.github/workflows/test.yml"
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
expect_file_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" "$file"; then
fail "$msg (missing: '$needle')"
fi
}
expect_file_matches() {
local file="$1"
local pattern="$2"
local msg="$3"
if ! grep -Eq -- "$pattern" "$file"; then
fail "$msg (pattern: '$pattern')"
fi
}
expect_file_not_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if grep -Fq -- "$needle" "$file"; then
fail "$msg (unexpected: '$needle')"
fi
}
expect_execution_contains() {
local file="$1"
local execution_id="$2"
local needle="$3"
local msg="$4"
local block
block="$(awk -v id="$execution_id" '
/<execution>/ { in_execution=1; block=$0 ORS; next }
in_execution { block = block $0 ORS }
/<\/execution>/ {
if (block ~ "<id>" id "</id>") {
print block
found=1
exit
}
in_execution=0
block=""
}
END {
if (!found) {
exit 1
}
}
' "$file")" || fail "$msg (execution not found: '$execution_id')"
if ! grep -Fq -- "$needle" <<<"$block"; then
fail "$msg (missing: '$needle' in execution '$execution_id')"
fi
}
test_parent_declares_advisory_quality_defaults() {
echo "Running test_parent_declares_advisory_quality_defaults"
expect_file_matches "$POM" "<spotbugs.version>[0-9]+(\\.[0-9]+)+</spotbugs.version>" "parent pom should pin SpotBugs with an explicit version"
expect_file_matches "$POM" "<jacoco.version>[0-9]+(\\.[0-9]+)+</jacoco.version>" "parent pom should pin JaCoCo with an explicit version"
expect_file_contains "$POM" "<ta4j.jacoco.line.minimum>0.80</ta4j.jacoco.line.minimum>" "line coverage threshold should be declared"
expect_file_contains "$POM" "<ta4j.jacoco.branch.minimum>0.80</ta4j.jacoco.branch.minimum>" "branch coverage threshold should be declared"
expect_file_not_contains "$POM" "<ta4j.spotbugs.failOnError>" "SpotBugs advisory mode should not rely on a shared property"
expect_file_not_contains "$POM" "<ta4j.jacoco.haltOnFailure>" "JaCoCo advisory mode should not rely on a shared property"
pass "test_parent_declares_advisory_quality_defaults"
}
test_parent_manages_quality_plugins_for_verify() {
echo "Running test_parent_manages_quality_plugins_for_verify"
expect_file_contains "$POM" "<artifactId>spotbugs-maven-plugin</artifactId>" "parent pom should manage SpotBugs"
expect_file_contains "$POM" "<artifactId>jacoco-maven-plugin</artifactId>" "parent pom should manage JaCoCo"
expect_file_contains "$POM" "<artifactId>exec-maven-plugin</artifactId>" "parent pom should skip root exec:java runs"
expect_file_contains "$POM" "<skip>true</skip>" "parent pom should skip exec:java on the aggregator"
expect_file_contains "$POM" "<quiet>true</quiet>" "SpotBugs should stay compact in verify logs"
expect_file_contains "$POM" "@{argLine}" "Surefire should late-bind the JaCoCo agent argLine"
expect_execution_contains "$POM" "spotbugs-check" "<phase>verify</phase>" "SpotBugs should stay wired into verify"
expect_execution_contains "$POM" "spotbugs-check" "<failOnError>false</failOnError>" "SpotBugs should stay advisory only for the verify-bound execution"
expect_execution_contains "$POM" "jacoco-check" "<phase>verify</phase>" "JaCoCo should stay wired into verify"
expect_execution_contains "$POM" "jacoco-check" "<haltOnFailure>false</haltOnFailure>" "JaCoCo should stay advisory only for the verify-bound execution"
pass "test_parent_manages_quality_plugins_for_verify"
}
test_modules_opt_in_to_managed_quality_plugins() {
echo "Running test_modules_opt_in_to_managed_quality_plugins"
expect_file_contains "$CORE_POM" "<artifactId>spotbugs-maven-plugin</artifactId>" "ta4j-core should opt into SpotBugs"
expect_file_contains "$CORE_POM" "<artifactId>jacoco-maven-plugin</artifactId>" "ta4j-core should opt into JaCoCo"
expect_file_contains "$EXAMPLES_POM" "<artifactId>spotbugs-maven-plugin</artifactId>" "ta4j-examples should opt into SpotBugs"
expect_file_contains "$EXAMPLES_POM" "<artifactId>jacoco-maven-plugin</artifactId>" "ta4j-examples should opt into JaCoCo"
expect_file_contains "$EXAMPLES_POM" "<exec.mainClass>ta4jexamples.Quickstart</exec.mainClass>" "ta4j-examples should default exec:java to Quickstart"
expect_file_contains "$EXAMPLES_POM" "<mainClass>\${exec.mainClass}</mainClass>" "ta4j-examples should allow exec:java main-class overrides"
pass "test_modules_opt_in_to_managed_quality_plugins"
}
test_ci_runs_verify_for_both_jobs() {
echo "Running test_ci_runs_verify_for_both_jobs"
expect_file_contains "$WORKFLOW" "run: xvfb-run mvn -B verify" "default CI job should run verify"
expect_file_contains "$WORKFLOW" "run: xvfb-run mvn -B verify -Dta4j.excludedTestTags=analysis-demo,elliott-macro-cycle-replay" "non-demo CI job should run verify"
pass "test_ci_runs_verify_for_both_jobs"
}
test_docs_point_to_real_maven_commands() {
echo "Running test_docs_point_to_real_maven_commands"
expect_file_contains "$ROOT/README.md" "Run \`mvn verify\` before opening or updating a pull request." "README should point contributors at the real verify command"
expect_file_contains "$ROOT/README.md" "mvn -pl ta4j-core -am spotbugs:check" "README should document the standalone SpotBugs loop"
expect_file_contains "$ROOT/README.md" "mvn -pl ta4j-core -am test jacoco:report jacoco:check" "README should document the standalone JaCoCo gate"
expect_file_contains "$ROOT/README.md" "mvn -pl ta4j-core -am -Dtest=BarSeriesManagerTest -Dsurefire.failIfNoSpecifiedTests=false test jacoco:report" "README should document a focused JaCoCo report-only loop"
expect_file_contains "$ROOT/README.md" "- [Build commands: Maven](#build-commands-maven)" "README table of contents should link to the renamed build section"
expect_file_contains "$ROOT/README.md" "mvn -pl ta4j-examples exec:java -Dexec.mainClass=ta4jexamples.backtesting.TradingRecordParityBacktest" "README should demonstrate overriding exec:java with a non-default example"
expect_file_contains "$ROOT/.github/CONTRIBUTING.md" "**Run this before opening or updating a PR:** \`mvn -B verify\`" "contributing guide should use verify as the canonical PR command"
expect_file_contains "$ROOT/.github/CONTRIBUTING.md" "mvn -pl ta4j-core -am spotbugs:check" "contributing guide should document the standalone SpotBugs loop"
expect_file_contains "$ROOT/.github/CONTRIBUTING.md" "mvn -pl ta4j-core -am test jacoco:report jacoco:check" "contributing guide should document the standalone JaCoCo gate"
expect_file_contains "$ROOT/.github/CONTRIBUTING.md" "mvn -B license:format formatter:format" "contributing guide should keep the formatter and license fix command"
expect_file_not_contains "$ROOT/README.md" "./mvnw" "README should not advertise a missing Maven Wrapper"
pass "test_docs_point_to_real_maven_commands"
}
test_parent_declares_advisory_quality_defaults
test_parent_manages_quality_plugins_for_verify
test_modules_opt_in_to_managed_quality_plugins
test_ci_runs_verify_for_both_jobs
test_docs_point_to_real_maven_commands
echo
echo "All quality scan config tests passed."
@@ -0,0 +1,305 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# test_release_helpers.sh
#
# Validates release workflow helper behavior used by GitHub Actions.
# =============================================================================
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/release/release_helpers.py"
PYTHON="${PYTHON:-python3}"
cleanup() {
if [[ -n "${TMP:-}" && -d "$TMP" ]]; then
rm -rf "$TMP"
fi
return 0
}
trap cleanup EXIT
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
run_test() {
TMP="$(mktemp -d "${TMPDIR:-/tmp}/release-helpers.XXXXXX")"
pushd "$TMP" >/dev/null || exit 1
}
finish_test() {
popd >/dev/null || exit 1
rm -rf "$TMP"
}
expect_file_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" "$file"; then
fail "$msg (missing: '$needle')"
fi
}
expect_json_value() {
local file="$1"
local filter="$2"
local expected="$3"
local actual
actual="$($PYTHON - "$file" "$filter" <<'PY'
import json
import sys
path, key = sys.argv[1:3]
with open(path, encoding="utf-8") as handle:
data = json.load(handle)
value = data
for part in key.split("."):
value = value[part]
if isinstance(value, bool):
print("true" if value else "false")
else:
print(value)
PY
)"
if [[ "$actual" != "$expected" ]]; then
fail "expected ${filter}=${expected}, got ${actual:-<missing>}"
fi
}
write_catalog_fixture() {
cat > catalog.json <<'EOF'
[
{
"id": "openai/gpt-4.1",
"name": "OpenAI GPT-4.1",
"publisher": "OpenAI",
"summary": "Large-context model",
"rate_limit_tier": "high",
"limits": {
"max_input_tokens": 1048576,
"max_output_tokens": 32768
},
"html_url": "https://github.com/marketplace/models/azure-openai/gpt-4-1"
}
]
EOF
}
test_catalog_preflight_accepts_configured_model() {
echo "Running test_catalog_preflight_accepts_configured_model"
run_test
write_catalog_fixture
GITHUB_OUTPUT=outputs.txt "$PYTHON" "$SCRIPT" catalog-preflight \
--model openai/gpt-4.1 \
--catalog-file catalog.json \
--output release-ai-model.json
expect_json_value release-ai-model.json id openai/gpt-4.1
expect_json_value release-ai-model.json max_input_tokens 1048576
expect_file_contains outputs.txt "model_id=openai/gpt-4.1" "catalog preflight should emit model id"
finish_test
pass "test_catalog_preflight_accepts_configured_model"
}
test_catalog_preflight_rejects_missing_model() {
echo "Running test_catalog_preflight_rejects_missing_model"
run_test
write_catalog_fixture
if "$PYTHON" "$SCRIPT" catalog-preflight \
--model openai/missing \
--catalog-file catalog.json \
--output release-ai-model.json >preflight.log 2>&1; then
fail "catalog preflight should reject an unavailable model"
fi
expect_file_contains preflight.log "openai/missing" "failure should name missing model"
finish_test
pass "test_catalog_preflight_rejects_missing_model"
}
test_parse_decision_normalizes_major_and_invalid_json() {
echo "Running test_parse_decision_normalizes_major_and_invalid_json"
run_test
cat > ai-content.txt <<'EOF'
```json
{"should_release":true,"bump":"major","confidence":0.91,"reason":"Breaking but pre-1.0 change","evidence":["public API changed"]}
```
EOF
"$PYTHON" "$SCRIPT" parse-decision --raw-file ai-content.txt --output decision.json --github-output outputs.txt
expect_json_value decision.json should_release true
expect_json_value decision.json bump minor
expect_file_contains outputs.txt "bump=minor" "major bumps should be downgraded in outputs"
cat > ai-content.txt <<'EOF'
{"should_release":"false","bump":"minor","confidence":0.7,"reason":"No release needed"}
EOF
"$PYTHON" "$SCRIPT" parse-decision --raw-file ai-content.txt --output string-false.json
expect_json_value string-false.json should_release false
expect_json_value string-false.json bump patch
cat > ai-content.txt <<'EOF'
{"should_release":"1","bump":"patch","confidence":0.7,"reason":"Release needed"}
EOF
"$PYTHON" "$SCRIPT" parse-decision --raw-file ai-content.txt --output string-true.json
expect_json_value string-true.json should_release true
cat > ai-content.txt <<'EOF'
{"should_release":"maybe","bump":"minor","confidence":0.7,"reason":"Ambiguous response"}
EOF
"$PYTHON" "$SCRIPT" parse-decision --raw-file ai-content.txt --output invalid-flag.json
expect_json_value invalid-flag.json should_release false
expect_json_value invalid-flag.json bump patch
expect_file_contains invalid-flag.json "invalid should_release 'maybe'" "invalid flag should be called out"
printf 'not-json' > ai-content.txt
"$PYTHON" "$SCRIPT" parse-decision --raw-file ai-content.txt --output invalid.json
expect_json_value invalid.json should_release false
expect_json_value invalid.json bump patch
expect_json_value invalid.json warning "Invalid AI JSON"
finish_test
pass "test_parse_decision_normalizes_major_and_invalid_json"
}
test_build_dossier_groups_and_truncates_diff() {
echo "Running test_build_dossier_groups_and_truncates_diff"
run_test
git init -q -b master
git config user.name "Test User"
git config user.email "test@example.com"
mkdir -p ta4j-core/src/main/java/org/ta4j/core scripts
cat > CHANGELOG.md <<'EOF'
## Unreleased
- Added release helper coverage.
EOF
cat > pom.xml <<'EOF'
<project><version>1.0.0-SNAPSHOT</version></project>
EOF
cat > ta4j-core/src/main/java/org/ta4j/core/Fixture.java <<'EOF'
package org.ta4j.core;
public class Fixture {
}
EOF
git add .
git commit -q -m "Initial"
git tag -a 1.0.0 -m "Release 1.0.0"
cat > ta4j-core/src/main/java/org/ta4j/core/Fixture.java <<'EOF'
package org.ta4j.core;
/**
* Fixture API.
*
* @since 1.0.1
*/
public class Fixture {
public String value() {
return "release-helper-dossier-with-enough-content-to-trigger-truncation";
}
}
EOF
git add .
git commit -q -m "Add fixture API"
"$PYTHON" "$SCRIPT" build-dossier \
--last-tag 1.0.0 \
--current-version 1.0.1-SNAPSHOT \
--pom-base 1.0.1 \
--max-diff-chars 120 \
--output release-dossier.md \
--audit-output release-audit.json
expect_file_contains release-dossier.md "production code" "dossier should group production code"
expect_file_contains release-dossier.md "Public API Signals" "dossier should include API signal section"
expect_file_contains release-dossier.md "[TRUNCATED" "dossier should make diff truncation explicit"
expect_json_value release-audit.json selected_diff_truncated true
finish_test
pass "test_build_dossier_groups_and_truncates_diff"
}
test_artifact_manifest_validates_expected_release_jars() {
echo "Running test_artifact_manifest_validates_expected_release_jars"
run_test
version=1.2.3
for file in \
"ta4j-core/target/ta4j-core-${version}.jar" \
"ta4j-core/target/ta4j-core-${version}-sources.jar" \
"ta4j-core/target/ta4j-core-${version}-javadoc.jar" \
"ta4j-core/target/ta4j-core-${version}-tests.jar" \
"ta4j-examples/target/ta4j-examples-${version}.jar" \
"ta4j-examples/target/ta4j-examples-${version}-sources.jar" \
"ta4j-examples/target/ta4j-examples-${version}-javadoc.jar"; do
mkdir -p "$(dirname "$file")"
: > "$file"
done
"$PYTHON" "$SCRIPT" artifact-manifest --version "$version" --output artifact-manifest.txt --strict
expect_file_contains artifact-manifest.txt "Missing release artifacts:" "manifest should include missing section"
expect_file_contains artifact-manifest.txt "- (none)" "manifest should report no missing artifacts"
: > "ta4j-core/target/unexpected.jar"
if "$PYTHON" "$SCRIPT" artifact-manifest --version "$version" --output artifact-manifest.txt --strict >manifest.log 2>&1; then
fail "strict artifact manifest should reject unexpected jars"
fi
expect_file_contains manifest.log "Unexpected target jars" "strict failure should name unexpected jars"
finish_test
pass "test_artifact_manifest_validates_expected_release_jars"
}
test_javadoc_warning_baseline_rejects_new_warnings() {
echo "Running test_javadoc_warning_baseline_rejects_new_warnings"
run_test
cat > baseline.txt <<'EOF'
ta4j-core/src/main/java/org/ta4j/core/Foo.java: warning: no @param for value
EOF
cat > release.log <<'EOF'
[WARNING] /home/runner/work/ta4j/ta4j/ta4j-core/src/main/java/org/ta4j/core/Foo.java: warning: no @param for value
[WARNING] /home/runner/work/ta4j/ta4j/ta4j-core/src/test/java/org/ta4j/core/BaseTradingRecordTest.java:[61,15] recordFill(org.ta4j.core.Trade) has been deprecated
EOF
"$PYTHON" "$SCRIPT" javadoc-warnings \
--baseline baseline.txt \
--output javadoc-warnings.txt \
--github-output outputs.txt \
--fail-on-new \
release.log
expect_file_contains outputs.txt "javadoc_warning_new_count=0" "baseline match should not report new warnings"
expect_file_contains javadoc-warnings.txt "current_count=1" "compiler warnings should not count as Javadoc warnings"
cat >> release.log <<'EOF'
[WARNING] /home/runner/work/ta4j/ta4j/ta4j-examples/src/main/java/ta4jexamples/FooExample.java: warning: no @return
EOF
if "$PYTHON" "$SCRIPT" javadoc-warnings \
--baseline baseline.txt \
--output javadoc-warnings.txt \
--fail-on-new \
release.log >javadoc.log 2>&1; then
fail "new Javadoc warning should fail the baseline check"
fi
expect_file_contains javadoc.log "New Javadoc warning" "baseline failure should name new warning"
finish_test
pass "test_javadoc_warning_baseline_rejects_new_warnings"
}
test_catalog_preflight_accepts_configured_model
test_catalog_preflight_rejects_missing_model
test_parse_decision_normalizes_major_and_invalid_json
test_build_dossier_groups_and_truncates_diff
test_artifact_manifest_validates_expected_release_jars
test_javadoc_warning_baseline_rejects_new_warnings
echo
echo "All release helper tests passed."
+377
View File
@@ -0,0 +1,377 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
WORKFLOWS="$ROOT/.github/workflows"
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
expect_file_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" "$file"; then
fail "$msg (missing: '$needle' in ${file#"$ROOT"/})"
fi
}
line_of() {
local file="$1"
local needle="$2"
local line
line="$(grep -nFm1 -- "$needle" "$file" || true)"
line="${line%%:*}"
if [[ -z "$line" ]]; then
fail "missing expected workflow line '$needle' in ${file#"$ROOT"/}"
fi
printf '%s\n' "$line"
}
input_section() {
local file="$1"
local input="$2"
awk -v needle=" ${input}:" '
index($0, needle) { active = 1 }
active { print }
active && index($0, "type: boolean") { exit }
' "$file"
}
workflow_section() {
local file="$1"
local start="$2"
local end="$3"
awk -v start="$start" -v end="$end" '
index($0, start) { active = 1 }
active { print }
active && index($0, end) { exit }
' "$file"
}
expect_section_contains() {
local section="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" <<<"$section"; then
fail "$msg (missing: '$needle')"
fi
}
assert_no_github_script_injected_binding_redeclarations() {
local workflow="$1"
awk -v file="${workflow#"$ROOT"/}" '
/uses: actions\/github-script@/ {
pending_script = 1
in_script = 0
next
}
pending_script && /^[[:space:]]*script: \|/ {
in_script = 1
next
}
in_script && /^[[:space:]]{6}- name:/ {
pending_script = 0
in_script = 0
}
in_script && /^[[:space:]]*(const|let|var)[[:space:]]+(core|github|context|glob|io|exec)[[:space:]]*=/ {
printf("[FAIL] %s redeclares actions/github-script injected binding at line %d: %s\n", file, NR, $0) > "/dev/stderr"
exit 1
}
' "$workflow"
}
test_maven_workflow_jobs_setup_jdk25_before_maven() {
echo "Running test_maven_workflow_jobs_setup_jdk25_before_maven"
local workflow
for workflow in "$WORKFLOWS"/*.yml; do
if ! grep -Eq '(^|[[:space:]"({;])xvfb-run[[:space:]]+mvn[[:space:]-]|(^|[[:space:]"({;])mvn[[:space:]-]' "$workflow"; then
continue
fi
awk -v file="${workflow#"$ROOT"/}" '
/^jobs:/ { in_jobs = 1; next }
in_jobs && /^ [A-Za-z0-9_-]+:$/ {
job = $1
sub(/:$/, "", job)
setup = 0
temurin = 0
jdk25 = 0
next
}
/^[^[:space:]]/ && $0 !~ /^jobs:/ {
in_jobs = 0
}
/uses: actions\/setup-java@v5/ {
setup = 1
temurin = 0
jdk25 = 0
}
setup && /distribution: temurin/ { temurin = 1 }
setup && /java-version: 25/ { jdk25 = 1 }
/^[[:space:]]*#/ { next }
/(^|[[:space:]"({;])xvfb-run[[:space:]]+mvn[[:space:]-]|(^|[[:space:]"({;])mvn[[:space:]-]/ {
if (!setup || !temurin || !jdk25) {
printf("[FAIL] %s job %s runs Maven before Temurin JDK 25 setup at line %d\n", file, job ? job : "(unknown)", NR) > "/dev/stderr"
exit 1
}
}
' "$workflow"
done
pass "test_maven_workflow_jobs_setup_jdk25_before_maven"
}
test_mutating_manual_workflows_default_to_dry_run() {
echo "Running test_mutating_manual_workflows_default_to_dry_run"
local workflow
for workflow in \
release-scheduler.yml \
prepare-release.yml \
publish-release.yml \
github-release.yml \
snapshot.yml \
release-health.yml; do
local file="$WORKFLOWS/$workflow"
local section
section="$(input_section "$file" dryRun)"
expect_section_contains "$section" "default: true" "${workflow} manual dryRun input should default true"
expect_section_contains "$section" "type: boolean" "${workflow} manual dryRun input should be typed as boolean"
done
pass "test_mutating_manual_workflows_default_to_dry_run"
}
test_official_triggers_normalize_to_non_dry_run() {
echo "Running test_official_triggers_normalize_to_non_dry_run"
expect_file_contains "$WORKFLOWS/release-scheduler.yml" 'if [ "$event" = "workflow_dispatch" ]; then' \
"release scheduler should branch normalization by event"
expect_file_contains "$WORKFLOWS/release-scheduler.yml" "raw=\"\${DRY_RUN_INPUT:-true}\"" \
"release scheduler manual default should normalize to dry-run"
expect_file_contains "$WORKFLOWS/release-scheduler.yml" "dry_run=false" \
"release scheduler scheduled runs should normalize to non-dry-run"
expect_file_contains "$WORKFLOWS/publish-release.yml" 'if [ "${{ github.event_name }}" = "pull_request" ]; then' \
"publish-release should normalize merged release PRs to non-dry-run"
expect_file_contains "$WORKFLOWS/github-release.yml" 'if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then' \
"github-release should keep tag pushes non-dry-run"
expect_file_contains "$WORKFLOWS/snapshot.yml" 'if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then' \
"snapshot should keep master pushes non-dry-run"
expect_file_contains "$WORKFLOWS/release-health.yml" 'if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then' \
"release-health should keep schedule/push/workflow-run triggers non-dry-run"
pass "test_official_triggers_normalize_to_non_dry_run"
}
test_downstream_dispatches_explicitly_pass_dry_run() {
echo "Running test_downstream_dispatches_explicitly_pass_dry_run"
local scheduler_dispatch
scheduler_dispatch="$(workflow_section "$WORKFLOWS/release-scheduler.yml" 'workflow_id: "prepare-release.yml"' "dryRun")"
expect_section_contains "$scheduler_dispatch" "dryRun" \
"release scheduler dispatch should pass dryRun to prepare-release"
local prepare_dispatch
prepare_dispatch="$(workflow_section "$WORKFLOWS/prepare-release.yml" 'workflow_id: "publish-release.yml"' "dryRun")"
expect_section_contains "$prepare_dispatch" "dryRun" \
"prepare-release dispatch should pass dryRun to publish-release"
local publish_dispatch
publish_dispatch="$(workflow_section "$WORKFLOWS/publish-release.yml" 'workflow_id: "snapshot.yml"' 'dryRun: "false"')"
expect_section_contains "$publish_dispatch" 'dryRun: "false"' \
"publish-release should explicitly dispatch snapshot publication as non-dry-run"
pass "test_downstream_dispatches_explicitly_pass_dry_run"
}
test_mutating_steps_remain_dry_run_gated() {
echo "Running test_mutating_steps_remain_dry_run_gated"
expect_file_contains "$WORKFLOWS/release-scheduler.yml" "if: always() && needs.analyze.result != 'skipped' && needs.analyze.outputs.dryRun != 'true'" \
"release scheduler discussion mutation should skip dry-run and skipped scheduled runs"
expect_file_contains "$WORKFLOWS/prepare-release.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
"prepare-release mutations should be dry-run gated"
expect_file_contains "$WORKFLOWS/prepare-release.yml" "Create or update removal-ready deprecation issues" \
"prepare-release should still have managed issue sync"
expect_file_contains "$WORKFLOWS/prepare-release.yml" "Create or update release PR" \
"prepare-release should still have release PR mutation"
expect_file_contains "$WORKFLOWS/publish-release.yml" "Deploy Release to Maven Central" \
"publish-release should still have Maven Central deployment"
expect_file_contains "$WORKFLOWS/publish-release.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
"publish-release mutations should be dry-run gated"
expect_file_contains "$WORKFLOWS/publish-release.yml" "if: always() && steps.dry_run.outputs.dryRun != 'true'" \
"publish-release discussion mutation should skip dry-run"
expect_file_contains "$WORKFLOWS/github-release.yml" "Create GitHub Release" \
"github-release should still publish GitHub releases"
expect_file_contains "$WORKFLOWS/github-release.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
"github-release publication should skip dry-run"
expect_file_contains "$WORKFLOWS/snapshot.yml" "Deploy Snapshot" \
"snapshot workflow should still publish snapshots"
expect_file_contains "$WORKFLOWS/snapshot.yml" "if: steps.dry_run.outputs.dryRun != 'true'" \
"snapshot deployment and publishing-secret steps should skip dry-run"
expect_file_contains "$WORKFLOWS/release-health.yml" "if: always() && steps.dry_run.outputs.dryRun != 'true'" \
"release-health discussion mutation should skip dry-run"
pass "test_mutating_steps_remain_dry_run_gated"
}
test_dry_run_summaries_and_audits_show_rerun_guidance() {
echo "Running test_dry_run_summaries_and_audits_show_rerun_guidance"
expect_file_contains "$WORKFLOWS/release-scheduler.yml" "release-scheduler-mutation-plan.txt" \
"release scheduler audit artifact should include the dry-run mutation plan"
expect_file_contains "$WORKFLOWS/prepare-release.yml" '"mutationPlan"' \
"prepare-release audit JSON should include mutation plan"
expect_file_contains "$WORKFLOWS/publish-release.yml" '"mutationPlan"' \
"publish-release audit JSON should include mutation plan"
expect_file_contains "$WORKFLOWS/github-release.yml" '"mutationPlan"' \
"github-release audit JSON should include mutation plan"
expect_file_contains "$WORKFLOWS/snapshot.yml" '"snapshotVersion"' \
"snapshot audit JSON should include computed snapshot version"
expect_file_contains "$WORKFLOWS/snapshot.yml" '"mutationPlan"' \
"snapshot audit JSON should include mutation plan"
expect_file_contains "$WORKFLOWS/release-health.yml" '"mutationPlan"' \
"release-health audit JSON should include mutation plan"
expect_file_contains "$WORKFLOWS/prepare-release.yml" "rerun guidance" \
"prepare-release summary should include rerun guidance"
expect_file_contains "$WORKFLOWS/publish-release.yml" "rerun guidance" \
"publish-release summary should include rerun guidance"
expect_file_contains "$WORKFLOWS/snapshot.yml" "rerun guidance" \
"snapshot summary should include rerun guidance"
pass "test_dry_run_summaries_and_audits_show_rerun_guidance"
}
test_snapshot_and_health_manual_dry_runs_do_not_mutate() {
echo "Running test_snapshot_and_health_manual_dry_runs_do_not_mutate"
local deploy_section
deploy_section="$(workflow_section "$WORKFLOWS/snapshot.yml" "Deploy Snapshot" "Snapshot publication summary")"
expect_section_contains "$deploy_section" "if: steps.dry_run.outputs.dryRun != 'true'" \
"snapshot deployment should skip dry-run"
local health_post_section
health_post_section="$(workflow_section "$WORKFLOWS/release-health.yml" "Post to Release Scheduler discussion" "with:")"
expect_section_contains "$health_post_section" "if: always() && steps.dry_run.outputs.dryRun != 'true'" \
"release-health discussion post should skip dry-run"
pass "test_snapshot_and_health_manual_dry_runs_do_not_mutate"
}
test_line_of_reports_missing_needles_cleanly() {
echo "Running test_line_of_reports_missing_needles_cleanly"
local tmp
local output
tmp="$(mktemp "${TMPDIR:-/tmp}/release-workflow-safety.XXXXXX")"
printf 'present\n' > "$tmp"
if output="$( (line_of "$tmp" "missing") 2>&1 )"; then
rm -f "$tmp"
fail "line_of should fail when the workflow line is missing"
fi
rm -f "$tmp"
expect_section_contains "$output" "missing expected workflow line 'missing'" \
"line_of should surface the explicit missing-line failure message"
pass "test_line_of_reports_missing_needles_cleanly"
}
test_publish_release_existing_tag_only_fails_real_runs() {
echo "Running test_publish_release_existing_tag_only_fails_real_runs"
expect_file_contains "$WORKFLOWS/publish-release.yml" \
"if: steps.check_tag.outputs.exists == 'true' && steps.dry_run.outputs.dryRun != 'true'" \
"publish-release should fail existing tags only during real runs"
expect_file_contains "$WORKFLOWS/publish-release.yml" "Report existing tag in dry-run mode" \
"publish-release should keep a dry-run audit path for existing tags"
expect_file_contains "$WORKFLOWS/publish-release.yml" "audit:existing_tag_dry_run" \
"publish-release dry-run tag detection should emit an audit line"
pass "test_publish_release_existing_tag_only_fails_real_runs"
}
test_github_release_preserves_workflow_support_checkout() {
echo "Running test_github_release_preserves_workflow_support_checkout"
local full_checkout_line
local support_checkout_line
local manifest_line
full_checkout_line="$(line_of "$WORKFLOWS/github-release.yml" "Checkout full history")"
support_checkout_line="$(line_of "$WORKFLOWS/github-release.yml" "Checkout workflow support files")"
manifest_line="$(line_of "$WORKFLOWS/github-release.yml" "workflow-support/scripts/release/release_helpers.py artifact-manifest")"
if (( support_checkout_line <= full_checkout_line )); then
fail "github-release should checkout workflow support after the release tag checkout"
fi
if (( manifest_line <= support_checkout_line )); then
fail "github-release should validate artifacts after workflow support checkout"
fi
expect_file_contains "$WORKFLOWS/github-release.yml" "path: workflow-support" \
"github-release should keep support helpers outside the release tag checkout"
pass "test_github_release_preserves_workflow_support_checkout"
}
test_github_script_blocks_do_not_redeclare_injected_bindings() {
echo "Running test_github_script_blocks_do_not_redeclare_injected_bindings"
local workflow
for workflow in "$WORKFLOWS"/*.yml; do
assert_no_github_script_injected_binding_redeclarations "$workflow"
done
pass "test_github_script_blocks_do_not_redeclare_injected_bindings"
}
test_github_script_binding_scan_rejects_bad_fixture() {
echo "Running test_github_script_binding_scan_rejects_bad_fixture"
local tmp
local output
tmp="$(mktemp "${TMPDIR:-/tmp}/release-workflow-safety.XXXXXX")"
cat > "$tmp" <<'YAML'
name: Bad fixture
jobs:
bad:
steps:
- name: Bad GitHub Script
uses: actions/github-script@v9
with:
script: |
const core = require("@actions/core");
YAML
if output="$(assert_no_github_script_injected_binding_redeclarations "$tmp" 2>&1)"; then
rm -f "$tmp"
fail "github-script injected binding scan should fail on a core redeclaration"
fi
rm -f "$tmp"
expect_section_contains "$output" "redeclares actions/github-script injected binding" \
"github-script injected binding scan should report the redeclaration"
pass "test_github_script_binding_scan_rejects_bad_fixture"
}
test_maven_workflow_jobs_setup_jdk25_before_maven
test_mutating_manual_workflows_default_to_dry_run
test_official_triggers_normalize_to_non_dry_run
test_downstream_dispatches_explicitly_pass_dry_run
test_mutating_steps_remain_dry_run_gated
test_dry_run_summaries_and_audits_show_rerun_guidance
test_snapshot_and_health_manual_dry_runs_do_not_mutate
test_line_of_reports_missing_needles_cleanly
test_publish_release_existing_tag_only_fails_real_runs
test_github_release_preserves_workflow_support_checkout
test_github_script_blocks_do_not_redeclare_injected_bindings
test_github_script_binding_scan_rejects_bad_fixture
+218
View File
@@ -0,0 +1,218 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# test_resolve_release_tags.sh
#
# Validates release tag baseline selection and first-parent diagnostics.
# =============================================================================
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/resolve-release-tags.sh"
cleanup() {
if [[ -n "${TMP:-}" && -d "$TMP" ]]; then
rm -rf "$TMP"
fi
return 0
}
trap cleanup EXIT
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
expect_output_value() {
local output="$1"
local key="$2"
local expected="$3"
local actual=""
actual="$(printf '%s\n' "$output" | awk -F= -v key="$key" '$1 == key { print substr($0, length($1) + 2); exit }')"
if [[ "$actual" != "$expected" ]]; then
fail "expected ${key}=${expected}, got ${actual:-<missing>}"
fi
}
run_test() {
TMP="$(mktemp -d)"
mkdir -p "$TMP/scripts"
cp "$SCRIPT" "$TMP/scripts/resolve-release-tags.sh"
chmod +x "$TMP/scripts/resolve-release-tags.sh"
pushd "$TMP" >/dev/null || exit 1
git init -q -b master
git config user.name "Test User"
git config user.email "test@example.com"
}
finish_test() {
popd >/dev/null || exit 1
rm -rf "$TMP"
}
commit_file() {
local path="$1"
local content="$2"
local message="$3"
local date="$4"
mkdir -p "$(dirname "$path")"
printf '%s\n' "$content" > "$path"
git add "$path"
GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" git commit -q -m "$message"
}
tag_release() {
local tag="$1"
local date="$2"
GIT_COMMITTER_DATE="$date" git tag -a "$tag" -m "Release $tag"
}
merge_branch() {
local branch="$1"
local message="$2"
local date="$3"
GIT_AUTHOR_DATE="$date" GIT_COMMITTER_DATE="$date" git merge --no-ff -m "$message" "$branch" >/dev/null
}
test_returns_none_when_no_release_tags_exist() {
echo "Running test_returns_none_when_no_release_tags_exist"
run_test
commit_file README.md "fixture" "Initial commit" "2024-01-01T00:00:00Z"
local out
out="$(scripts/resolve-release-tags.sh HEAD)"
expect_output_value "$out" "latest_tag" "none"
expect_output_value "$out" "last_reachable_tag" "none"
expect_output_value "$out" "last_first_parent_tag" "none"
expect_output_value "$out" "latest_tag_reachable" "n/a"
finish_test
pass "test_returns_none_when_no_release_tags_exist"
}
test_prefers_reachable_tag_for_release_baseline() {
echo "Running test_prefers_reachable_tag_for_release_baseline"
run_test
commit_file README.md "base" "Initial commit" "2024-01-01T00:00:00Z"
tag_release "0.21.0" "2024-01-02T00:00:00Z"
commit_file main.txt "after 0.21.0" "Master commit after 0.21.0" "2024-01-03T00:00:00Z"
git checkout -q -b release/0.22.4
commit_file release.txt "release payload" "Release 0.22.4" "2024-01-04T00:00:00Z"
tag_release "0.22.4" "2024-01-05T00:00:00Z"
git checkout -q master
merge_branch "release/0.22.4" "Merge release/0.22.4" "2024-01-06T00:00:00Z"
local out
out="$(scripts/resolve-release-tags.sh HEAD)"
expect_output_value "$out" "latest_tag" "0.22.4"
expect_output_value "$out" "last_reachable_tag" "0.22.4"
expect_output_value "$out" "last_first_parent_tag" "0.21.0"
expect_output_value "$out" "latest_tag_reachable" "true"
finish_test
pass "test_prefers_reachable_tag_for_release_baseline"
}
test_reports_unreachable_latest_tag() {
echo "Running test_reports_unreachable_latest_tag"
run_test
commit_file README.md "base" "Initial commit" "2024-02-01T00:00:00Z"
tag_release "0.22.3" "2024-02-02T00:00:00Z"
commit_file main.txt "master work" "Master commit" "2024-02-03T00:00:00Z"
git checkout -q -b release/0.22.4
commit_file release.txt "detached release" "Detached release commit" "2024-02-04T00:00:00Z"
tag_release "0.22.4" "2024-02-05T00:00:00Z"
git checkout -q master
local out
out="$(scripts/resolve-release-tags.sh HEAD)"
expect_output_value "$out" "latest_tag" "0.22.4"
expect_output_value "$out" "last_reachable_tag" "0.22.3"
expect_output_value "$out" "last_first_parent_tag" "0.22.3"
expect_output_value "$out" "latest_tag_reachable" "false"
finish_test
pass "test_reports_unreachable_latest_tag"
}
test_falls_back_to_v_prefixed_tags() {
echo "Running test_falls_back_to_v_prefixed_tags"
run_test
commit_file README.md "base" "Initial commit" "2024-03-01T00:00:00Z"
tag_release "v1.2.3" "2024-03-02T00:00:00Z"
local out
out="$(scripts/resolve-release-tags.sh HEAD)"
expect_output_value "$out" "latest_tag" "v1.2.3"
expect_output_value "$out" "last_reachable_tag" "v1.2.3"
expect_output_value "$out" "last_first_parent_tag" "v1.2.3"
expect_output_value "$out" "latest_tag_reachable" "true"
finish_test
pass "test_falls_back_to_v_prefixed_tags"
}
test_prefers_numeric_tags_over_v_prefixed_tags() {
echo "Running test_prefers_numeric_tags_over_v_prefixed_tags"
run_test
commit_file README.md "base" "Initial commit" "2024-04-01T00:00:00Z"
tag_release "1.2.3" "2024-04-02T00:00:00Z"
commit_file main.txt "master work" "Master commit" "2024-04-03T00:00:00Z"
tag_release "v9.9.9" "2024-04-04T00:00:00Z"
local out
out="$(scripts/resolve-release-tags.sh HEAD)"
expect_output_value "$out" "latest_tag" "1.2.3"
expect_output_value "$out" "last_reachable_tag" "1.2.3"
expect_output_value "$out" "last_first_parent_tag" "1.2.3"
expect_output_value "$out" "latest_tag_reachable" "true"
finish_test
pass "test_prefers_numeric_tags_over_v_prefixed_tags"
}
test_returns_head_values_when_target_ref_is_missing() {
echo "Running test_returns_head_values_when_target_ref_is_missing"
run_test
commit_file README.md "base" "Initial commit" "2024-05-01T00:00:00Z"
tag_release "0.30.0" "2024-05-02T00:00:00Z"
local out
out="$(scripts/resolve-release-tags.sh refs/remotes/origin/master)"
expect_output_value "$out" "latest_tag" "0.30.0"
expect_output_value "$out" "last_reachable_tag" "0.30.0"
expect_output_value "$out" "last_first_parent_tag" "0.30.0"
expect_output_value "$out" "latest_tag_reachable" "true"
finish_test
pass "test_returns_head_values_when_target_ref_is_missing"
}
test_returns_none_when_no_release_tags_exist
test_prefers_reachable_tag_for_release_baseline
test_reports_unreachable_latest_tag
test_falls_back_to_v_prefixed_tags
test_prefers_numeric_tags_over_v_prefixed_tags
test_returns_head_values_when_target_ref_is_missing
echo
echo "All resolve-release-tags tests passed."
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/release/release_helpers.py"
PYTHON="${PYTHON:-python3}"
cleanup() {
if [[ -n "${TMP:-}" && -d "$TMP" ]]; then
rm -rf "$TMP"
fi
return 0
}
trap cleanup EXIT
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
new_temp_dir() {
mktemp -d "${TMPDIR:-/tmp}/snapshot-publication.XXXXXX"
}
expect_output_value() {
local file="$1"
local key="$2"
local expected="$3"
local actual=""
actual="$(awk -F= -v key="$key" '$1 == key { print substr($0, length($1) + 2); exit }' "$file")"
if [[ "$actual" != "$expected" ]]; then
fail "expected ${key}=${expected}, got ${actual:-<missing>}"
fi
}
expect_file_contains() {
local file="$1"
local expected="$2"
if ! grep -Fq -- "$expected" "$file"; then
fail "expected ${file} to contain: ${expected}"
fi
}
write_metadata_fixture() {
local path="$1"
local latest="$2"
local versions="$3"
local last_updated="$4"
cat > "$path" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.ta4j</groupId>
<artifactId>ta4j-parent</artifactId>
<versioning>
<latest>${latest}</latest>
<versions>
${versions}
</versions>
<lastUpdated>${last_updated}</lastUpdated>
</versioning>
</metadata>
EOF
}
test_snapshot_version_present() {
echo "Running test_snapshot_version_present"
TMP="$(new_temp_dir)"
local metadata_file="$TMP/metadata.xml"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication.json"
write_metadata_fixture "$metadata_file" "0.22.7-SNAPSHOT" $' <version>0.22.6-SNAPSHOT</version>\n <version>0.22.7-SNAPSHOT</version>' "20260506001534"
"$PYTHON" "$SCRIPT" snapshot-publication \
--version "0.22.7-SNAPSHOT" \
--metadata-file "$metadata_file" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication" "true"
expect_output_value "$github_output" "snapshot_publication_latest" "0.22.7-SNAPSHOT"
expect_output_value "$github_output" "snapshot_publication_last_updated" "20260506001534"
rm -rf "$TMP"
pass "test_snapshot_version_present"
}
test_snapshot_version_missing() {
echo "Running test_snapshot_version_missing"
TMP="$(new_temp_dir)"
local metadata_file="$TMP/metadata.xml"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication.json"
write_metadata_fixture "$metadata_file" "0.22.7-SNAPSHOT" $' <version>0.22.6-SNAPSHOT</version>\n <version>0.22.7-SNAPSHOT</version>' "20260506001534"
"$PYTHON" "$SCRIPT" snapshot-publication \
--version "0.22.8-SNAPSHOT" \
--metadata-file "$metadata_file" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication" "false"
expect_output_value "$github_output" "snapshot_publication_latest" "0.22.7-SNAPSHOT"
rm -rf "$TMP"
pass "test_snapshot_version_missing"
}
test_non_snapshot_version_returns_na() {
echo "Running test_non_snapshot_version_returns_na"
TMP="$(new_temp_dir)"
local metadata_file="$TMP/metadata.xml"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication.json"
write_metadata_fixture "$metadata_file" "0.22.7-SNAPSHOT" $' <version>0.22.7-SNAPSHOT</version>' "20260506001534"
"$PYTHON" "$SCRIPT" snapshot-publication \
--version "0.22.7" \
--metadata-file "$metadata_file" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication" "n/a"
rm -rf "$TMP"
pass "test_non_snapshot_version_returns_na"
}
test_malformed_metadata_returns_unknown() {
echo "Running test_malformed_metadata_returns_unknown"
TMP="$(new_temp_dir)"
local metadata_file="$TMP/metadata.xml"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication.json"
printf '%s\n' '<metadata><versioning>' > "$metadata_file"
"$PYTHON" "$SCRIPT" snapshot-publication \
--version "0.22.7-SNAPSHOT" \
--metadata-file "$metadata_file" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication" "unknown"
rm -rf "$TMP"
pass "test_malformed_metadata_returns_unknown"
}
test_non_https_metadata_url_returns_unknown() {
echo "Running test_non_https_metadata_url_returns_unknown"
TMP="$(new_temp_dir)"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication.json"
"$PYTHON" "$SCRIPT" snapshot-publication \
--version "0.22.7-SNAPSHOT" \
--metadata-url "file:///tmp/snapshot-publication.xml" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication" "unknown"
expect_file_contains "$output_file" "\"error\": \"--metadata-url must use https\""
rm -rf "$TMP"
pass "test_non_https_metadata_url_returns_unknown"
}
test_snapshot_publication_policy_defers_push_runs() {
echo "Running test_snapshot_publication_policy_defers_push_runs"
TMP="$(new_temp_dir)"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication-policy.json"
"$PYTHON" "$SCRIPT" snapshot-publication-policy \
--event-name "push" \
--workflow-name "" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication_enforced" "false"
expect_output_value "$github_output" "snapshot_publication_pending_reason" "awaiting snapshot workflow completion before treating snapshot metadata drift as authoritative"
rm -rf "$TMP"
pass "test_snapshot_publication_policy_defers_push_runs"
}
test_snapshot_publication_policy_enforces_snapshot_workflow_runs() {
echo "Running test_snapshot_publication_policy_enforces_snapshot_workflow_runs"
TMP="$(new_temp_dir)"
local github_output="$TMP/github-output.txt"
local output_file="$TMP/snapshot-publication-policy.json"
"$PYTHON" "$SCRIPT" snapshot-publication-policy \
--event-name "workflow_run" \
--workflow-name "Publish Snapshot to Maven Central" \
--output "$output_file" \
--github-output "$github_output" >/dev/null
expect_output_value "$github_output" "snapshot_publication_enforced" "true"
expect_output_value "$github_output" "snapshot_publication_pending_reason" ""
rm -rf "$TMP"
pass "test_snapshot_publication_policy_enforces_snapshot_workflow_runs"
}
test_snapshot_version_present
test_snapshot_version_missing
test_non_snapshot_version_returns_na
test_malformed_metadata_returns_unknown
test_non_https_metadata_url_returns_unknown
test_snapshot_publication_policy_defers_push_runs
test_snapshot_publication_policy_enforces_snapshot_workflow_runs
echo
echo "All snapshot publication tests passed."
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# test_validate_central_metadata.sh
#
# Validates behavior of scripts/validate-central-metadata.sh
# =============================================================================
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT/scripts/validate-central-metadata.sh"
cleanup() {
if [[ -n "${TMP:-}" && -d "$TMP" ]]; then
rm -rf "$TMP"
fi
return 0
}
trap cleanup EXIT
fail() { echo "[FAIL] $1" >&2; exit 1; }
pass() { echo "[PASS] $1"; }
expect_contains() {
local haystack="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" <<<"$haystack"; then
fail "$msg (missing: '$needle')"
fi
}
expect_file_contains() {
local file="$1"
local needle="$2"
local msg="$3"
if ! grep -Fq -- "$needle" "$file"; then
fail "$msg (missing: '$needle')"
fi
}
create_fixture() {
local include_developers="$1"
mkdir -p ta4j-core ta4j-examples scripts
cp "$SCRIPT" scripts/validate-central-metadata.sh
chmod +x scripts/validate-central-metadata.sh
cat > pom.xml <<EOF
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ta4j</groupId>
<artifactId>ta4j-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Ta4j Parent</name>
<description>Fixture description</description>
<url>https://github.com/ta4j/ta4j</url>
<licenses>
<license>
<name>MIT License</name>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/ta4j/ta4j.git</connection>
<url>https://github.com/ta4j/ta4j</url>
</scm>
EOF
if [[ "$include_developers" == "true" ]]; then
cat >> pom.xml <<'EOF'
<developers>
<developer>
<id>maintainer</id>
<name>Maintainer</name>
</developer>
</developers>
EOF
fi
cat >> pom.xml <<'EOF'
<modules>
<module>ta4j-core</module>
<module>ta4j-examples</module>
</modules>
</project>
EOF
for module in ta4j-core ta4j-examples; do
cat > "${module}/pom.xml" <<EOF
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ta4j</groupId>
<artifactId>ta4j-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>${module}</artifactId>
</project>
EOF
done
}
run_test() {
TMP="$(mktemp -d)"
pushd "$TMP" >/dev/null || exit 1
}
finish_test() {
popd >/dev/null || exit 1
rm -rf "$TMP"
}
test_passes_with_complete_metadata() {
echo "Running test_passes_with_complete_metadata"
run_test
create_fixture "true"
local out
out="$(scripts/validate-central-metadata.sh)"
expect_contains "$out" "validation passed" "validator should pass for complete metadata"
finish_test
pass "test_passes_with_complete_metadata"
}
test_fails_when_developers_missing() {
echo "Running test_fails_when_developers_missing"
run_test
create_fixture "false"
local out_file="validator.log"
if scripts/validate-central-metadata.sh >"$out_file" 2>&1; then
fail "validator should fail when developers metadata is missing"
fi
expect_file_contains "$out_file" "developers[0].id" "should mention missing developer id"
expect_file_contains "$out_file" "developers[0].name" "should mention missing developer name"
expect_file_contains "$out_file" "validation failed" "should print summary failure"
finish_test
pass "test_fails_when_developers_missing"
}
test_passes_with_complete_metadata
test_fails_when_developers_missing
echo
echo "All validate-central-metadata tests passed."