This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch feature/staleness-test-coverage-and-release-notes in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git
commit 8ddae1f763b21a15c4ad118d71b4137be4be7ba8 Author: Wu Sheng <[email protected]> AuthorDate: Sun Mar 15 19:55:55 2026 +0800 Add replacement class coverage check and 0.2.0 release notes - ReplacementClassStalenessTest: add auto-discovery pass that scans oap-libs-for-graalvm/ for untracked same-FQCN replacements - release.sh: verify changes/changes.md has release notes before proceeding - Add 0.2.0 changes section with upstream sync commit reference --- changes/changes.md | 42 +++++++ .../graalvm/ReplacementClassStalenessTest.java | 132 +++++++++++++++++++-- release/release.sh | 12 ++ 3 files changed, 176 insertions(+), 10 deletions(-) diff --git a/changes/changes.md b/changes/changes.md index 89f1294..9836004 100644 --- a/changes/changes.md +++ b/changes/changes.md @@ -1,5 +1,47 @@ # Changes +## 0.2.0 + +### Highlights + +Upgrade to the latest Apache SkyWalking OAP server, with documentation restructure and CI/CD improvements. + +### Upstream Sync + +- Sync SkyWalking submodule to upstream commit `64a1795d8a`. + +### Documentation + +- Add user-facing docs: Quick Start, Supported Features, FAQ. +- Move internal build-time docs to `docs/internals/`. +- Update `docs/README.md` with "For Users" / "For Contributors" sections and official doc site link. +- Add Docker Hub README (`docker/DOCKERHUB_README.md`). +- Add release guide (`docs/release-guide.md`). +- Update root `README.md` with project intro, quick start, and image registry table. + +### CI/CD + +- Push Docker images to Docker Hub (release only) in addition to GHCR. +- Docker Hub only receives `latest` and version tags — no commit SHA tags. +- Add `.asf.yaml` branch protection. +- PR-only `cancel-in-progress` to avoid cancelling release builds. + +### Release Tooling + +- `release/release.sh`: auto-create SVN `graalvm-distro` directory if it doesn't exist. +- `release/full-release.sh`: end-to-end release script. +- Generate vote email template with GPG signer info and submodule commit IDs. + +### Testing + +- Replacement class staleness detector: add auto-discovery coverage check for untracked same-FQCN replacements in `oap-libs-for-graalvm/`. + +### E2E Tests + +- Update BanyanDB to `e1ba421` (fixes Zipkin `minDuration` trace query). +- Bump Istio to 1.28.0. +- Add Baseline e2e test case. + ## 0.1.0 ### Highlights diff --git a/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java b/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java index d49d15f..dc8e7f7 100644 --- a/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java +++ b/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java @@ -24,8 +24,11 @@ import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Properties; +import java.util.Set; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -34,10 +37,18 @@ import static org.junit.jupiter.api.Assertions.fail; /** * Staleness detector for same-FQCN replacement classes. * - * <p>Tracks the SHA-256 of each upstream source file that has a same-FQCN - * replacement in this project. If an upstream file changes (e.g. after a - * {@code skywalking/} submodule update), this test fails with a clear message - * indicating which replacement(s) need review and update. + * <p>Two verification passes: + * <ol> + * <li><b>SHA-256 check</b>: For every entry in + * {@code replacement-source-sha256.properties}, verify the upstream source + * file still exists at the recorded path and its content hash matches. + * A mismatch means the replacement needs review and update.</li> + * <li><b>Coverage check</b>: Scan all {@code .java} files under + * {@code oap-libs-for-graalvm/} with {@code org.apache.skywalking} package, + * map each to its upstream counterpart in {@code skywalking/}, and verify + * it is tracked in the properties file. An untracked replacement means + * someone added a same-FQCN override without recording its upstream SHA.</li> + * </ol> * * <p>SHA-256 hashes are recorded in {@code replacement-source-sha256.properties} * (same pattern as {@code precompiled-yaml-sha256.properties}). @@ -46,6 +57,12 @@ class ReplacementClassStalenessTest { private static final String PROPS_RESOURCE = "replacement-source-sha256.properties"; + /** + * Base package prefix for same-FQCN replacements. Only classes under this + * package in oap-libs-for-graalvm are expected to have upstream counterparts. + */ + private static final String SKYWALKING_PACKAGE = "org/apache/skywalking/"; + @Test void allReplacementSourcesMatchRecordedSha256() throws Exception { Properties props = loadProperties(); @@ -75,17 +92,21 @@ class ReplacementClassStalenessTest { } StringBuilder msg = new StringBuilder(); + if (!missing.isEmpty()) { + msg.append("Upstream source files no longer exist — replacement is orphaned:\n"); + for (String m : missing) { + msg.append(" ").append(m).append('\n'); + } + msg.append(" Action: check if upstream renamed/moved/deleted the class,\n"); + msg.append(" then update or remove the replacement and its properties entry.\n\n"); + } if (!mismatches.isEmpty()) { msg.append("Upstream source files changed — review and update these replacements:\n"); for (String m : mismatches) { msg.append(m).append('\n'); } - } - if (!missing.isEmpty()) { - msg.append("Upstream source files not found:\n"); - for (String m : missing) { - msg.append(" ").append(m).append('\n'); - } + msg.append(" Action: diff the upstream change, update the replacement class,\n"); + msg.append(" then run: shasum -a 256 <path> to update the properties file.\n\n"); } if (msg.length() > 0) { @@ -93,6 +114,97 @@ class ReplacementClassStalenessTest { } } + /** + * Scan all replacement .java files under oap-libs-for-graalvm/ with + * org.apache.skywalking package and verify each has a corresponding entry + * in replacement-source-sha256.properties. This catches new replacements + * that were added without recording the upstream SHA. + */ + @Test + void allReplacementClassesAreTracked() throws Exception { + Properties props = loadProperties(); + Path projectRoot = Path.of(System.getProperty("user.dir")).getParent(); + Path libsDir = projectRoot.resolve("oap-libs-for-graalvm"); + + if (!Files.isDirectory(libsDir)) { + return; // skip if directory doesn't exist (e.g. clean checkout) + } + + // Collect all tracked upstream paths for quick lookup + Set<String> trackedUpstreamPaths = new HashSet<>(); + for (String key : props.stringPropertyNames()) { + trackedUpstreamPaths.add(key); + } + + // Scan oap-libs-for-graalvm for replacement .java files + List<String> untracked = new ArrayList<>(); + try (Stream<Path> walk = Files.walk(libsDir)) { + walk.filter(p -> p.toString().endsWith(".java")) + .filter(p -> p.toString().contains("src/main/java/")) + .forEach(replacementFile -> { + // Extract the relative path after src/main/java/ + String fullPath = replacementFile.toString(); + int srcIdx = fullPath.indexOf("src/main/java/"); + if (srcIdx < 0) return; + String classRelPath = fullPath.substring(srcIdx + "src/main/java/".length()); + + // Only check org.apache.skywalking classes + if (!classRelPath.startsWith(SKYWALKING_PACKAGE)) return; + + // Try to find the upstream source file + String upstreamPath = resolveUpstreamPath(projectRoot, classRelPath); + if (upstreamPath != null && !trackedUpstreamPaths.contains(upstreamPath)) { + untracked.add(String.format( + " replacement: %s%n upstream: %s", + projectRoot.relativize(replacementFile), upstreamPath)); + } + }); + } + + if (!untracked.isEmpty()) { + StringBuilder msg = new StringBuilder(); + msg.append("Replacement classes not tracked in ").append(PROPS_RESOURCE).append(":\n"); + for (String u : untracked) { + msg.append(u).append('\n'); + } + msg.append(" Action: add the upstream path and its SHA-256 to ").append(PROPS_RESOURCE).append('\n'); + msg.append(" Run: shasum -a 256 <upstream-path>\n"); + fail(msg.toString()); + } + } + + /** + * Resolve the upstream source path for a replacement class. + * Searches under skywalking/ for a .java file matching the same relative + * class path (e.g. org/apache/skywalking/.../Foo.java). + * + * @return relative path from project root (e.g. skywalking/oap-server/.../Foo.java), + * or null if no upstream source found + */ + private static String resolveUpstreamPath(Path projectRoot, String classRelPath) { + Path skywalking = projectRoot.resolve("skywalking"); + if (!Files.isDirectory(skywalking)) return null; + + try (Stream<Path> walk = Files.walk(skywalking)) { + return walk + .filter(p -> p.toString().endsWith(".java")) + .filter(p -> !p.toString().contains("/target/")) + .filter(p -> !p.toString().contains("/test/")) + .filter(p -> { + String s = p.toString(); + int idx = s.indexOf("src/main/java/"); + if (idx < 0) return false; + String rel = s.substring(idx + "src/main/java/".length()); + return rel.equals(classRelPath); + }) + .map(p -> projectRoot.relativize(p).toString()) + .findFirst() + .orElse(null); + } catch (IOException e) { + return null; + } + } + private Properties loadProperties() throws IOException { Properties props = new Properties(); try (InputStream is = getClass().getClassLoader() diff --git a/release/release.sh b/release/release.sh index 4458e9d..02d2b3f 100755 --- a/release/release.sh +++ b/release/release.sh @@ -112,6 +112,18 @@ git rev-parse "${TAG}" >/dev/null 2>&1 \ gh release view "${TAG}" --repo "${REPO}" >/dev/null 2>&1 \ || error "GitHub Release for ${TAG} not found. Run the CI release workflow first." +# Verify changes/changes.md contains a section for this version +CHANGES_FILE="${REPO_ROOT}/changes/changes.md" +[[ -f "${CHANGES_FILE}" ]] || error "changes/changes.md not found" +if ! grep -q "^## ${VERSION}$" "${CHANGES_FILE}"; then + error "changes/changes.md does not contain a '## ${VERSION}' section. Add release notes before releasing." +fi +# Verify the section has content (at least one non-empty line before next ## or EOF) +SECTION_CONTENT=$(sed -n "/^## ${VERSION}$/,/^## /p" "${CHANGES_FILE}" | sed '1d;/^## /d' | grep -v '^$' | head -1) +[[ -n "${SECTION_CONTENT}" ]] \ + || error "changes/changes.md has a '## ${VERSION}' section but it is empty. Add release notes." +log "changes/changes.md: found release notes for ${VERSION}" + # ─── Step 1: Prepare release directory ──────────────────────────────────────── log "Preparing release directory..." rm -rf "${SCRIPT_DIR}/${RELEASE_DIR}"
