This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git
The following commit(s) were added to refs/heads/main by this push:
new d05b707 Add replacement class coverage check and 0.2.0 release notes
(#11)
d05b707 is described below
commit d05b707634436a8b02ec33fe8c2cbac6390ac935
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Sun Mar 15 21:23:26 2026 +0800
Add replacement class coverage check and 0.2.0 release notes (#11)
- 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}"