mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-15 11:14:31 +08:00
🐛 fix(test): 统一 JVM fixture JDK 工具链 (#912)
## 背景 JVM HTTP 和 JMX 真实 fixture 会分别从 PATH 查找 java 与 javac。在多 JDK 环境中可能使用 Java 8 运行 javac 17 的编译产物,readiness 最终只报告 EOF。 ## 变更 - 新增 HTTP、JMX、Agent 共用的测试 JDK 工具链解析 helper - 优先使用完整 JAVA_HOME,否则选择 javac 同目录的 java 和 jar - 启动前校验 java 与 javac major version - fixture 启动失败时报告 stderr、退出状态、工具路径和版本 - 补充 JAVA_HOME、javac sibling、版本不一致和诊断信息测试 ## 影响范围 仅修改 internal/jvm 测试代码,不影响产品运行时逻辑。 ## 验证 - Issue 指定 HTTP/JMX 真实 round-trip 通过 - Agent 真实 round-trip 通过 - go test ./internal/jvm -count=1 -timeout=10m 通过 Closes #894
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package jvm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -105,16 +104,6 @@ func TestAgentProviderGetMonitoringSnapshotDecodesResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAgentProviderRealAgentRoundTrip(t *testing.T) {
|
||||
if _, err := exec.LookPath("java"); err != nil {
|
||||
t.Skipf("java 不可用,跳过真实 Agent 集成测试: %v", err)
|
||||
}
|
||||
if _, err := exec.LookPath("javac"); err != nil {
|
||||
t.Skipf("javac 不可用,跳过真实 Agent 集成测试: %v", err)
|
||||
}
|
||||
if _, err := exec.LookPath("jar"); err != nil {
|
||||
t.Skipf("jar 不可用,跳过真实 Agent 集成测试: %v", err)
|
||||
}
|
||||
|
||||
provider := NewAgentProvider()
|
||||
fixture := startAgentFixture(t)
|
||||
cfg := newAgentProviderTestConfig(fixture.baseURL+"/gonavi/agent/jvm", 5)
|
||||
@@ -212,24 +201,12 @@ func TestAgentProviderRealAgentRoundTrip(t *testing.T) {
|
||||
type agentFixtureProcess struct {
|
||||
port int
|
||||
baseURL string
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func startAgentFixture(t *testing.T) agentFixtureProcess {
|
||||
t.Helper()
|
||||
|
||||
javaBin, err := exec.LookPath("java")
|
||||
if err != nil {
|
||||
t.Fatalf("look up java failed: %v", err)
|
||||
}
|
||||
javacBin, err := exec.LookPath("javac")
|
||||
if err != nil {
|
||||
t.Fatalf("look up javac failed: %v", err)
|
||||
}
|
||||
jarBin, err := exec.LookPath("jar")
|
||||
if err != nil {
|
||||
t.Fatalf("look up jar failed: %v", err)
|
||||
}
|
||||
toolchain := requireJVMFixtureToolchain(t, true)
|
||||
|
||||
classesDir := filepath.Join(t.TempDir(), "agent-fixture-classes")
|
||||
if err := os.MkdirAll(classesDir, 0o755); err != nil {
|
||||
@@ -244,12 +221,7 @@ func startAgentFixture(t *testing.T) agentFixtureProcess {
|
||||
t.Fatalf("expected agent fixture java files under %s", sourceRoot)
|
||||
}
|
||||
|
||||
compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
|
||||
compileCmd := exec.Command(javacBin, compileArgs...)
|
||||
output, err := compileCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("compile agent fixture failed: %v\n%s", err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
compileJVMFixture(t, toolchain, "agent", classesDir, javaFiles)
|
||||
|
||||
manifestPath := filepath.Join(t.TempDir(), "agent-manifest.mf")
|
||||
manifest := strings.Join([]string{
|
||||
@@ -275,60 +247,23 @@ func startAgentFixture(t *testing.T) agentFixtureProcess {
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(agentJar)
|
||||
})
|
||||
jarCmd := exec.Command(jarBin, "cmf", manifestPath, agentJar, "-C", classesDir, "com")
|
||||
output, err = jarCmd.CombinedOutput()
|
||||
jarCmd := exec.Command(toolchain.JarBin, "cmf", manifestPath, agentJar, "-C", classesDir, "com")
|
||||
output, err := jarCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("package agent jar failed: %v\n%s", err, strings.TrimSpace(string(output)))
|
||||
t.Fatalf("package agent jar failed: %v; output: %s; toolchain: %s", err, nonEmptyJVMFixtureText(strings.TrimSpace(string(output)), "<empty>"), toolchain.summary())
|
||||
}
|
||||
|
||||
port := reserveTCPPort(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cmd := exec.CommandContext(
|
||||
ctx,
|
||||
javaBin,
|
||||
process, stdout := startJVMFixtureCommand(
|
||||
t,
|
||||
toolchain,
|
||||
"agent",
|
||||
fmt.Sprintf("-javaagent:%s=port=%d,token=secret-token", agentJar, port),
|
||||
"-cp",
|
||||
classesDir,
|
||||
"com.gonavi.fixture.AgentHostApp",
|
||||
)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("agent fixture stdout pipe failed: %v", err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start agent fixture failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
_ = cmd.Wait()
|
||||
})
|
||||
|
||||
ready := make(chan error, 1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
if strings.TrimSpace(scanner.Text()) == "AGENT_READY" {
|
||||
ready <- nil
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
ready <- fmt.Errorf("agent fixture readiness read failed: %w", err)
|
||||
return
|
||||
}
|
||||
ready <- fmt.Errorf("agent fixture terminated before readiness signal")
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ready:
|
||||
if err != nil {
|
||||
t.Fatalf("wait agent fixture ready failed: %v", err)
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Fatal("agent fixture did not become ready within 20s")
|
||||
}
|
||||
waitForJVMFixtureReady(t, process, stdout, toolchain, "agent", "AGENT_READY", 20*time.Second)
|
||||
|
||||
waitForTest(t, 10*time.Second, func() error {
|
||||
conn, dialErr := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 500*time.Millisecond)
|
||||
@@ -342,7 +277,6 @@ func startAgentFixture(t *testing.T) agentFixtureProcess {
|
||||
return agentFixtureProcess{
|
||||
port: port,
|
||||
baseURL: fmt.Sprintf("http://127.0.0.1:%d", port),
|
||||
cmd: cmd,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
448
internal/jvm/fixture_toolchain_test.go
Normal file
448
internal/jvm/fixture_toolchain_test.go
Normal file
@@ -0,0 +1,448 @@
|
||||
package jvm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errJVMFixtureToolchainUnavailable = errors.New("JDK toolchain unavailable")
|
||||
|
||||
var jvmFixtureVersionPattern = regexp.MustCompile(`(?i)(?:java|openjdk|javac)(?:\s+version)?\s+"?([0-9]+)(?:\.([0-9]+))?`)
|
||||
|
||||
type jvmFixtureToolchain struct {
|
||||
JavaBin string
|
||||
JavacBin string
|
||||
JarBin string
|
||||
JavaVersion string
|
||||
JavacVersion string
|
||||
Major int
|
||||
}
|
||||
|
||||
func (toolchain jvmFixtureToolchain) summary() string {
|
||||
return fmt.Sprintf(
|
||||
"java=%s (%s), javac=%s (%s), major=%d",
|
||||
toolchain.JavaBin,
|
||||
compactJVMFixtureVersion(toolchain.JavaVersion),
|
||||
toolchain.JavacBin,
|
||||
compactJVMFixtureVersion(toolchain.JavacVersion),
|
||||
toolchain.Major,
|
||||
)
|
||||
}
|
||||
|
||||
type jvmFixtureToolchainResolver struct {
|
||||
getenv func(string) string
|
||||
lookPath func(string) (string, error)
|
||||
stat func(string) (os.FileInfo, error)
|
||||
version func(context.Context, string) (string, int, error)
|
||||
}
|
||||
|
||||
func defaultJVMFixtureToolchainResolver() jvmFixtureToolchainResolver {
|
||||
return jvmFixtureToolchainResolver{
|
||||
getenv: os.Getenv,
|
||||
lookPath: exec.LookPath,
|
||||
stat: os.Stat,
|
||||
version: readJVMFixtureToolVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func requireJVMFixtureToolchain(t *testing.T, requireJar bool) jvmFixtureToolchain {
|
||||
t.Helper()
|
||||
|
||||
toolchain, err := defaultJVMFixtureToolchainResolver().resolve(context.Background(), requireJar)
|
||||
if errors.Is(err, errJVMFixtureToolchainUnavailable) {
|
||||
t.Skipf("一致 JDK 工具链不可用,跳过真实 JVM fixture 测试: %v", err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("解析 JVM fixture JDK 工具链失败: %v", err)
|
||||
}
|
||||
return toolchain
|
||||
}
|
||||
|
||||
func (resolver jvmFixtureToolchainResolver) resolve(ctx context.Context, requireJar bool) (jvmFixtureToolchain, error) {
|
||||
if javaHome := strings.TrimSpace(resolver.getenv("JAVA_HOME")); javaHome != "" {
|
||||
binDir := filepath.Join(javaHome, "bin")
|
||||
if toolchain, ok, err := resolver.resolveBinDir(ctx, binDir, requireJar); ok || err != nil {
|
||||
return toolchain, err
|
||||
}
|
||||
}
|
||||
|
||||
javacBin, err := resolver.lookPath(jvmFixtureBinaryName("javac"))
|
||||
if err != nil {
|
||||
return jvmFixtureToolchain{}, fmt.Errorf("%w: javac not found on PATH: %v", errJVMFixtureToolchainUnavailable, err)
|
||||
}
|
||||
binDir := filepath.Dir(javacBin)
|
||||
if toolchain, ok, resolveErr := resolver.resolveBinDir(ctx, binDir, requireJar); ok || resolveErr != nil {
|
||||
return toolchain, resolveErr
|
||||
}
|
||||
|
||||
javaBin, err := resolver.lookPath(jvmFixtureBinaryName("java"))
|
||||
if err != nil {
|
||||
return jvmFixtureToolchain{}, fmt.Errorf("%w: java not found beside %s or on PATH: %v", errJVMFixtureToolchainUnavailable, javacBin, err)
|
||||
}
|
||||
jarBin := ""
|
||||
if requireJar {
|
||||
return jvmFixtureToolchain{}, fmt.Errorf("JDK toolchain beside %s does not contain %s", javacBin, jvmFixtureBinaryName("jar"))
|
||||
}
|
||||
return resolver.validate(ctx, javaBin, javacBin, jarBin)
|
||||
}
|
||||
|
||||
func (resolver jvmFixtureToolchainResolver) resolveBinDir(ctx context.Context, binDir string, requireJar bool) (jvmFixtureToolchain, bool, error) {
|
||||
javaBin := filepath.Join(binDir, jvmFixtureBinaryName("java"))
|
||||
javacBin := filepath.Join(binDir, jvmFixtureBinaryName("javac"))
|
||||
if !resolver.isFile(javaBin) || !resolver.isFile(javacBin) {
|
||||
return jvmFixtureToolchain{}, false, nil
|
||||
}
|
||||
jarBin := ""
|
||||
if requireJar {
|
||||
jarBin = filepath.Join(binDir, jvmFixtureBinaryName("jar"))
|
||||
if !resolver.isFile(jarBin) {
|
||||
return jvmFixtureToolchain{}, false, nil
|
||||
}
|
||||
}
|
||||
toolchain, err := resolver.validate(ctx, javaBin, javacBin, jarBin)
|
||||
return toolchain, true, err
|
||||
}
|
||||
|
||||
func (resolver jvmFixtureToolchainResolver) isFile(path string) bool {
|
||||
info, err := resolver.stat(path)
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
func (resolver jvmFixtureToolchainResolver) validate(ctx context.Context, javaBin, javacBin, jarBin string) (jvmFixtureToolchain, error) {
|
||||
javaVersion, javaMajor, err := resolver.version(ctx, javaBin)
|
||||
if err != nil {
|
||||
return jvmFixtureToolchain{}, fmt.Errorf("read java version from %s: %w", javaBin, err)
|
||||
}
|
||||
javacVersion, javacMajor, err := resolver.version(ctx, javacBin)
|
||||
if err != nil {
|
||||
return jvmFixtureToolchain{}, fmt.Errorf("read javac version from %s: %w", javacBin, err)
|
||||
}
|
||||
if javaMajor != javacMajor {
|
||||
return jvmFixtureToolchain{}, fmt.Errorf(
|
||||
"java/javac major version mismatch: java=%s (%s, major=%d), javac=%s (%s, major=%d)",
|
||||
javaBin,
|
||||
compactJVMFixtureVersion(javaVersion),
|
||||
javaMajor,
|
||||
javacBin,
|
||||
compactJVMFixtureVersion(javacVersion),
|
||||
javacMajor,
|
||||
)
|
||||
}
|
||||
return jvmFixtureToolchain{
|
||||
JavaBin: javaBin,
|
||||
JavacBin: javacBin,
|
||||
JarBin: jarBin,
|
||||
JavaVersion: javaVersion,
|
||||
JavacVersion: javacVersion,
|
||||
Major: javaMajor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func jvmFixtureBinaryName(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return name + ".exe"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func readJVMFixtureToolVersion(parent context.Context, binary string) (string, int, error) {
|
||||
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := exec.CommandContext(ctx, binary, "-version").CombinedOutput()
|
||||
text := strings.TrimSpace(string(output))
|
||||
if err != nil {
|
||||
return text, 0, fmt.Errorf("%w; output: %s", err, nonEmptyJVMFixtureText(text, "<empty>"))
|
||||
}
|
||||
major, err := parseJVMFixtureMajorVersion(text)
|
||||
if err != nil {
|
||||
return text, 0, err
|
||||
}
|
||||
return text, major, nil
|
||||
}
|
||||
|
||||
func parseJVMFixtureMajorVersion(output string) (int, error) {
|
||||
matches := jvmFixtureVersionPattern.FindStringSubmatch(output)
|
||||
if len(matches) < 2 {
|
||||
return 0, fmt.Errorf("unrecognized Java version output: %q", compactJVMFixtureVersion(output))
|
||||
}
|
||||
major, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse Java major version from %q: %w", matches[1], err)
|
||||
}
|
||||
if major == 1 && len(matches) > 2 && matches[2] != "" {
|
||||
major, err = strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse legacy Java major version from %q: %w", matches[2], err)
|
||||
}
|
||||
}
|
||||
return major, nil
|
||||
}
|
||||
|
||||
func compactJVMFixtureVersion(version string) string {
|
||||
return strings.Join(strings.Fields(version), " ")
|
||||
}
|
||||
|
||||
func nonEmptyJVMFixtureText(value, fallback string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func compileJVMFixture(t *testing.T, toolchain jvmFixtureToolchain, label, classesDir string, javaFiles []string) {
|
||||
t.Helper()
|
||||
|
||||
compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
|
||||
output, err := exec.Command(toolchain.JavacBin, compileArgs...).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("compile %s fixture failed: %v; output: %s; toolchain: %s", label, err, nonEmptyJVMFixtureText(strings.TrimSpace(string(output)), "<empty>"), toolchain.summary())
|
||||
}
|
||||
}
|
||||
|
||||
type jvmFixtureCommand struct {
|
||||
cmd *exec.Cmd
|
||||
cancel context.CancelFunc
|
||||
stderr bytes.Buffer
|
||||
waitOnce sync.Once
|
||||
waitErr error
|
||||
}
|
||||
|
||||
func startJVMFixtureCommand(t *testing.T, toolchain jvmFixtureToolchain, label string, args ...string) (*jvmFixtureCommand, io.ReadCloser) {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cmd := exec.CommandContext(ctx, toolchain.JavaBin, args...)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
cancel()
|
||||
t.Fatalf("%s fixture stdout pipe failed: %v; toolchain: %s", label, err, toolchain.summary())
|
||||
}
|
||||
process := &jvmFixtureCommand{cmd: cmd, cancel: cancel}
|
||||
cmd.Stderr = &process.stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
t.Fatalf("start %s fixture failed: %v; toolchain: %s", label, err, toolchain.summary())
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
process.stopAndWait()
|
||||
})
|
||||
return process, stdout
|
||||
}
|
||||
|
||||
func (process *jvmFixtureCommand) stopAndWait() error {
|
||||
process.cancel()
|
||||
process.waitOnce.Do(func() {
|
||||
process.waitErr = process.cmd.Wait()
|
||||
})
|
||||
return process.waitErr
|
||||
}
|
||||
|
||||
func waitForJVMFixtureReady(t *testing.T, process *jvmFixtureCommand, stdout io.Reader, toolchain jvmFixtureToolchain, label, expectedLine string, timeout time.Duration) {
|
||||
t.Helper()
|
||||
|
||||
ready := make(chan error, 1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
if strings.TrimSpace(scanner.Text()) == expectedLine {
|
||||
ready <- nil
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
ready <- fmt.Errorf("readiness read failed: %w", err)
|
||||
return
|
||||
}
|
||||
ready <- io.EOF
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ready:
|
||||
if err != nil {
|
||||
waitErr := process.stopAndWait()
|
||||
t.Fatalf("%v", formatJVMFixtureStartError(label, err, waitErr, process.stderr.String(), toolchain))
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
waitErr := process.stopAndWait()
|
||||
t.Fatalf("%v", formatJVMFixtureStartError(label, fmt.Errorf("did not become ready within %s", timeout), waitErr, process.stderr.String(), toolchain))
|
||||
}
|
||||
}
|
||||
|
||||
func formatJVMFixtureStartError(label string, cause, waitErr error, stderr string, toolchain jvmFixtureToolchain) error {
|
||||
exitStatus := "success"
|
||||
if waitErr != nil {
|
||||
exitStatus = waitErr.Error()
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"%s fixture readiness failed: %w; exit status: %s; stderr: %s; toolchain: %s",
|
||||
label,
|
||||
cause,
|
||||
exitStatus,
|
||||
nonEmptyJVMFixtureText(strings.TrimSpace(stderr), "<empty>"),
|
||||
toolchain.summary(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestJVMFixtureToolchainUsesJavaBesideJavac(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
javacBin := filepath.Join(binDir, jvmFixtureBinaryName("javac"))
|
||||
siblingJava := filepath.Join(binDir, jvmFixtureBinaryName("java"))
|
||||
pathJava8 := filepath.Join(t.TempDir(), jvmFixtureBinaryName("java"))
|
||||
for _, path := range []string{javacBin, siblingJava, pathJava8} {
|
||||
if err := os.WriteFile(path, []byte("fixture"), 0o755); err != nil {
|
||||
t.Fatalf("write fake JVM tool %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
resolver := fakeJVMFixtureToolchainResolver(t, "", map[string]string{
|
||||
jvmFixtureBinaryName("javac"): javacBin,
|
||||
jvmFixtureBinaryName("java"): pathJava8,
|
||||
}, map[string]struct {
|
||||
version string
|
||||
major int
|
||||
}{
|
||||
javacBin: {version: "javac 17.0.15", major: 17},
|
||||
siblingJava: {version: `openjdk version "17.0.15"`, major: 17},
|
||||
pathJava8: {version: `java version "1.8.0_431"`, major: 8},
|
||||
})
|
||||
|
||||
toolchain, err := resolver.resolve(context.Background(), false)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve() error = %v", err)
|
||||
}
|
||||
if toolchain.JavaBin != siblingJava || toolchain.JavacBin != javacBin || toolchain.Major != 17 {
|
||||
t.Fatalf("resolve() = %#v, want javac sibling toolchain", toolchain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJVMFixtureToolchainPrefersCompleteJavaHome(t *testing.T) {
|
||||
javaHome := t.TempDir()
|
||||
binDir := filepath.Join(javaHome, "bin")
|
||||
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
||||
t.Fatalf("create fake JAVA_HOME bin: %v", err)
|
||||
}
|
||||
javaBin := filepath.Join(binDir, jvmFixtureBinaryName("java"))
|
||||
javacBin := filepath.Join(binDir, jvmFixtureBinaryName("javac"))
|
||||
jarBin := filepath.Join(binDir, jvmFixtureBinaryName("jar"))
|
||||
for _, path := range []string{javaBin, javacBin, jarBin} {
|
||||
if err := os.WriteFile(path, []byte("fixture"), 0o755); err != nil {
|
||||
t.Fatalf("write fake JAVA_HOME tool %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
resolver := fakeJVMFixtureToolchainResolver(t, javaHome, map[string]string{}, map[string]struct {
|
||||
version string
|
||||
major int
|
||||
}{
|
||||
javaBin: {version: `openjdk version "21.0.7"`, major: 21},
|
||||
javacBin: {version: "javac 21.0.7", major: 21},
|
||||
})
|
||||
|
||||
toolchain, err := resolver.resolve(context.Background(), true)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve() error = %v", err)
|
||||
}
|
||||
if toolchain.JavaBin != javaBin || toolchain.JavacBin != javacBin || toolchain.JarBin != jarBin || toolchain.Major != 21 {
|
||||
t.Fatalf("resolve() = %#v, want JAVA_HOME toolchain", toolchain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJVMFixtureToolchainRejectsMismatchedFallback(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
javacBin := filepath.Join(binDir, jvmFixtureBinaryName("javac"))
|
||||
pathJava8 := filepath.Join(t.TempDir(), jvmFixtureBinaryName("java"))
|
||||
for _, path := range []string{javacBin, pathJava8} {
|
||||
if err := os.WriteFile(path, []byte("fixture"), 0o755); err != nil {
|
||||
t.Fatalf("write fake JVM tool %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
resolver := fakeJVMFixtureToolchainResolver(t, "", map[string]string{
|
||||
jvmFixtureBinaryName("javac"): javacBin,
|
||||
jvmFixtureBinaryName("java"): pathJava8,
|
||||
}, map[string]struct {
|
||||
version string
|
||||
major int
|
||||
}{
|
||||
javacBin: {version: "javac 17.0.15", major: 17},
|
||||
pathJava8: {version: `java version "1.8.0_431"`, major: 8},
|
||||
})
|
||||
|
||||
_, err := resolver.resolve(context.Background(), false)
|
||||
if err == nil || !strings.Contains(err.Error(), "major version mismatch") || !strings.Contains(err.Error(), "major=8") || !strings.Contains(err.Error(), "major=17") {
|
||||
t.Fatalf("resolve() error = %v, want explicit version mismatch", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJVMFixtureMajorVersion(t *testing.T) {
|
||||
cases := map[string]int{
|
||||
`java version "1.8.0_431"`: 8,
|
||||
`openjdk version "17.0.15"`: 17,
|
||||
`javac 21.0.7`: 21,
|
||||
}
|
||||
for output, want := range cases {
|
||||
if got, err := parseJVMFixtureMajorVersion(output); err != nil || got != want {
|
||||
t.Fatalf("parseJVMFixtureMajorVersion(%q) = %d, %v; want %d", output, got, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatJVMFixtureStartErrorIncludesDiagnostics(t *testing.T) {
|
||||
toolchain := jvmFixtureToolchain{
|
||||
JavaBin: `C:\jdk-17\bin\java.exe`,
|
||||
JavacBin: `C:\jdk-17\bin\javac.exe`,
|
||||
JavaVersion: `openjdk version "17.0.15"`,
|
||||
JavacVersion: "javac 17.0.15",
|
||||
Major: 17,
|
||||
}
|
||||
err := formatJVMFixtureStartError("endpoint", io.EOF, errors.New("exit status 1"), "UnsupportedClassVersionError", toolchain)
|
||||
for _, want := range []string{"endpoint", "EOF", "exit status 1", "UnsupportedClassVersionError", "java.exe", "javac.exe", "17.0.15"} {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Fatalf("diagnostic error %q missing %q", err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fakeJVMFixtureToolchainResolver(t *testing.T, javaHome string, paths map[string]string, versions map[string]struct {
|
||||
version string
|
||||
major int
|
||||
}) jvmFixtureToolchainResolver {
|
||||
t.Helper()
|
||||
return jvmFixtureToolchainResolver{
|
||||
getenv: func(name string) string {
|
||||
if name == "JAVA_HOME" {
|
||||
return javaHome
|
||||
}
|
||||
return ""
|
||||
},
|
||||
lookPath: func(name string) (string, error) {
|
||||
if path := paths[name]; path != "" {
|
||||
return path, nil
|
||||
}
|
||||
return "", exec.ErrNotFound
|
||||
},
|
||||
stat: os.Stat,
|
||||
version: func(_ context.Context, binary string) (string, int, error) {
|
||||
version, ok := versions[binary]
|
||||
if !ok {
|
||||
return "", 0, fmt.Errorf("unexpected version lookup: %s", binary)
|
||||
}
|
||||
return version.version, version.major, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package jvm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -409,13 +407,6 @@ func TestHTTPProviderReturnsTimeoutError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPProviderRealEndpointRoundTrip(t *testing.T) {
|
||||
if _, err := exec.LookPath("java"); err != nil {
|
||||
t.Skipf("java 不可用,跳过真实 Endpoint 集成测试: %v", err)
|
||||
}
|
||||
if _, err := exec.LookPath("javac"); err != nil {
|
||||
t.Skipf("javac 不可用,跳过真实 Endpoint 集成测试: %v", err)
|
||||
}
|
||||
|
||||
provider := NewHTTPProvider()
|
||||
fixture := startEndpointFixture(t)
|
||||
cfg := newHTTPProviderTestConfig(fixture.baseURL+"/manage/jvm", 5)
|
||||
@@ -506,20 +497,12 @@ func TestHTTPProviderRealEndpointRoundTrip(t *testing.T) {
|
||||
type endpointFixtureProcess struct {
|
||||
port int
|
||||
baseURL string
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func startEndpointFixture(t *testing.T) endpointFixtureProcess {
|
||||
t.Helper()
|
||||
|
||||
javaBin, err := exec.LookPath("java")
|
||||
if err != nil {
|
||||
t.Fatalf("look up java failed: %v", err)
|
||||
}
|
||||
javacBin, err := exec.LookPath("javac")
|
||||
if err != nil {
|
||||
t.Fatalf("look up javac failed: %v", err)
|
||||
}
|
||||
toolchain := requireJVMFixtureToolchain(t, false)
|
||||
|
||||
classesDir := filepath.Join(t.TempDir(), "endpoint-fixture-classes")
|
||||
if err := os.MkdirAll(classesDir, 0o755); err != nil {
|
||||
@@ -534,52 +517,11 @@ func startEndpointFixture(t *testing.T) endpointFixtureProcess {
|
||||
t.Fatalf("expected endpoint fixture java files under %s", sourceRoot)
|
||||
}
|
||||
|
||||
compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
|
||||
compileCmd := exec.Command(javacBin, compileArgs...)
|
||||
output, err := compileCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("compile endpoint fixture failed: %v\n%s", err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
compileJVMFixture(t, toolchain, "endpoint", classesDir, javaFiles)
|
||||
|
||||
port := reserveTCPPort(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cmd := exec.CommandContext(ctx, javaBin, "-cp", classesDir, "com.gonavi.fixture.EndpointTestServer", fmt.Sprintf("%d", port))
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("endpoint fixture stdout pipe failed: %v", err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start endpoint fixture failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
_ = cmd.Wait()
|
||||
})
|
||||
|
||||
ready := make(chan error, 1)
|
||||
go func() {
|
||||
line, readErr := bufio.NewReader(stdout).ReadString('\n')
|
||||
if readErr != nil {
|
||||
ready <- fmt.Errorf("endpoint fixture readiness read failed: %w", readErr)
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(line) != "READY" {
|
||||
ready <- fmt.Errorf("unexpected endpoint fixture readiness line: %q", strings.TrimSpace(line))
|
||||
return
|
||||
}
|
||||
ready <- nil
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ready:
|
||||
if err != nil {
|
||||
t.Fatalf("wait endpoint fixture ready failed: %v", err)
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Fatal("endpoint fixture did not become ready within 20s")
|
||||
}
|
||||
process, stdout := startJVMFixtureCommand(t, toolchain, "endpoint", "-cp", classesDir, "com.gonavi.fixture.EndpointTestServer", fmt.Sprintf("%d", port))
|
||||
waitForJVMFixtureReady(t, process, stdout, toolchain, "endpoint", "READY", 20*time.Second)
|
||||
|
||||
waitForTest(t, 10*time.Second, func() error {
|
||||
conn, dialErr := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 500*time.Millisecond)
|
||||
@@ -593,7 +535,6 @@ func startEndpointFixture(t *testing.T) endpointFixtureProcess {
|
||||
return endpointFixtureProcess{
|
||||
port: port,
|
||||
baseURL: fmt.Sprintf("http://127.0.0.1:%d", port),
|
||||
cmd: cmd,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package jvm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -280,13 +278,6 @@ func TestJMXProviderGetValueRejectsUnknownResourcePath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJMXProviderRealJMXRoundTrip(t *testing.T) {
|
||||
if _, err := exec.LookPath("java"); err != nil {
|
||||
t.Skipf("java 不可用,跳过真实 JMX 集成测试: %v", err)
|
||||
}
|
||||
if _, err := exec.LookPath("javac"); err != nil {
|
||||
t.Skipf("javac 不可用,跳过真实 JMX 集成测试: %v", err)
|
||||
}
|
||||
|
||||
provider := NewJMXProvider()
|
||||
monitoringProvider, ok := provider.(MonitoringCapableProvider)
|
||||
if !ok {
|
||||
@@ -760,20 +751,12 @@ func TestJMXProviderRealJMXRoundTrip(t *testing.T) {
|
||||
|
||||
type jmxFixtureProcess struct {
|
||||
port int
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func startJMXFixture(t *testing.T) jmxFixtureProcess {
|
||||
t.Helper()
|
||||
|
||||
javaBin, err := exec.LookPath("java")
|
||||
if err != nil {
|
||||
t.Fatalf("look up java failed: %v", err)
|
||||
}
|
||||
javacBin, err := exec.LookPath("javac")
|
||||
if err != nil {
|
||||
t.Fatalf("look up javac failed: %v", err)
|
||||
}
|
||||
toolchain := requireJVMFixtureToolchain(t, false)
|
||||
|
||||
classesDir := filepath.Join(t.TempDir(), "fixture-classes")
|
||||
if err := os.MkdirAll(classesDir, 0o755); err != nil {
|
||||
@@ -788,18 +771,10 @@ func startJMXFixture(t *testing.T) jmxFixtureProcess {
|
||||
t.Fatalf("expected fixture java files under %s", sourceRoot)
|
||||
}
|
||||
|
||||
compileArgs := append([]string{"-encoding", "UTF-8", "-d", classesDir}, javaFiles...)
|
||||
compileCmd := exec.Command(javacBin, compileArgs...)
|
||||
output, err := compileCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("compile fixture failed: %v\n%s", err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
compileJVMFixture(t, toolchain, "JMX", classesDir, javaFiles)
|
||||
|
||||
port := reserveTCPPort(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cmd := exec.CommandContext(ctx, javaBin,
|
||||
process, stdout := startJVMFixtureCommand(t, toolchain, "JMX",
|
||||
fmt.Sprintf("-Dcom.sun.management.jmxremote.port=%d", port),
|
||||
fmt.Sprintf("-Dcom.sun.management.jmxremote.rmi.port=%d", port),
|
||||
"-Dcom.sun.management.jmxremote.authenticate=false",
|
||||
@@ -810,40 +785,7 @@ func startJMXFixture(t *testing.T) jmxFixtureProcess {
|
||||
"-cp", classesDir,
|
||||
"com.gonavi.fixture.JMXTestServer",
|
||||
)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("fixture stdout pipe failed: %v", err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("start fixture failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
_ = cmd.Wait()
|
||||
})
|
||||
|
||||
ready := make(chan error, 1)
|
||||
go func() {
|
||||
line, readErr := bufio.NewReader(stdout).ReadString('\n')
|
||||
if readErr != nil {
|
||||
ready <- fmt.Errorf("fixture readiness read failed: %w", readErr)
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(line) != "READY" {
|
||||
ready <- fmt.Errorf("unexpected fixture readiness line: %q", strings.TrimSpace(line))
|
||||
return
|
||||
}
|
||||
ready <- nil
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ready:
|
||||
if err != nil {
|
||||
t.Fatalf("wait fixture ready failed: %v", err)
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Fatal("fixture did not become ready within 20s")
|
||||
}
|
||||
waitForJVMFixtureReady(t, process, stdout, toolchain, "JMX", "READY", 20*time.Second)
|
||||
|
||||
waitForTest(t, 10*time.Second, func() error {
|
||||
conn, dialErr := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 500*time.Millisecond)
|
||||
@@ -854,7 +796,7 @@ func startJMXFixture(t *testing.T) jmxFixtureProcess {
|
||||
return nil
|
||||
})
|
||||
|
||||
return jmxFixtureProcess{port: port, cmd: cmd}
|
||||
return jmxFixtureProcess{port: port}
|
||||
}
|
||||
|
||||
func waitForTest(t *testing.T, timeout time.Duration, fn func() error) {
|
||||
|
||||
Reference in New Issue
Block a user