feat: discover default AI test toolchain

This commit is contained in:
张家训
2026-08-05 14:10:04 +08:00
parent 6bb665048c
commit 52e478a2be
4 changed files with 179 additions and 4 deletions
@@ -19,6 +19,7 @@ import java.util.Properties;
public class AiTestSettingsService { public class AiTestSettingsService {
private final Path settingsFile; private final Path settingsFile;
private final ToolchainSettingsDetector detector;
@Autowired @Autowired
public AiTestSettingsService(WorkUtilsProperties properties) { public AiTestSettingsService(WorkUtilsProperties properties) {
@@ -29,20 +30,31 @@ public class AiTestSettingsService {
this(paths.resolve("data", "ai-test")); this(paths.resolve("data", "ai-test"));
} }
AiTestSettingsService(ApplicationPaths paths, ToolchainSettingsDetector detector) {
this(paths.resolve("data", "ai-test"), detector);
}
private AiTestSettingsService(Path aiTestDataDirectory) { private AiTestSettingsService(Path aiTestDataDirectory) {
this(aiTestDataDirectory, ToolchainSettingsDetector.system());
}
AiTestSettingsService(Path aiTestDataDirectory, ToolchainSettingsDetector detector) {
this.settingsFile = aiTestDataDirectory.resolve("settings.properties"); this.settingsFile = aiTestDataDirectory.resolve("settings.properties");
this.detector = detector;
} }
public AiTestSettings load() throws IOException { public AiTestSettings load() throws IOException {
if (Files.notExists(settingsFile)) { if (Files.notExists(settingsFile)) {
return AiTestSettings.defaults(); AiTestSettings detected = detector.complete(AiTestSettings.defaults());
save(detected);
return detected;
} }
Properties values = new Properties(); Properties values = new Properties();
try (InputStream input = Files.newInputStream(settingsFile)) { try (InputStream input = Files.newInputStream(settingsFile)) {
values.load(input); values.load(input);
} }
AiTestSettings defaults = AiTestSettings.defaults(); AiTestSettings defaults = AiTestSettings.defaults();
return new AiTestSettings( AiTestSettings loaded = new AiTestSettings(
Path.of(values.getProperty("jdk8.home", defaults.jdk8Home().toString())), Path.of(values.getProperty("jdk8.home", defaults.jdk8Home().toString())),
Path.of(values.getProperty("maven.home", defaults.mavenHome().toString())), Path.of(values.getProperty("maven.home", defaults.mavenHome().toString())),
values.getProperty("codefree.command", defaults.codeFreeCommand()), values.getProperty("codefree.command", defaults.codeFreeCommand()),
@@ -52,6 +64,11 @@ public class AiTestSettingsService {
Math.toIntExact(defaults.codeFreeTimeout().toSeconds()))), Math.toIntExact(defaults.codeFreeTimeout().toSeconds()))),
Duration.ofSeconds(integer(values, "maven.timeout.seconds", Duration.ofSeconds(integer(values, "maven.timeout.seconds",
Math.toIntExact(defaults.mavenTimeout().toSeconds())))); Math.toIntExact(defaults.mavenTimeout().toSeconds()))));
AiTestSettings completed = detector.complete(loaded);
if (!completed.equals(loaded)) {
save(completed);
}
return completed;
} }
public void save(AiTestSettings settings) throws IOException { public void save(AiTestSettings settings) throws IOException {
@@ -0,0 +1,112 @@
package com.example.workutils.biz.aitest.config;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.Supplier;
public final class ToolchainSettingsDetector {
private static final Path WINDOWS_JDK8 = Path.of("C:/Program Files/Java/jdk-1.8");
private static final Path WINDOWS_MAVEN = Path.of("D:/bestpay-maven/apache-maven-3.8.6");
private final Map<String, String> environment;
private final Map<String, String> properties;
private final Supplier<String> mavenVersion;
private final Predicate<Path> directoryExists;
private final String osName;
public ToolchainSettingsDetector(Map<String, String> environment, Map<String, String> properties,
Supplier<String> mavenVersion, Predicate<Path> directoryExists,
String osName) {
this.environment = environment;
this.properties = properties;
this.mavenVersion = mavenVersion;
this.directoryExists = directoryExists;
this.osName = osName;
}
public static ToolchainSettingsDetector system() {
return new ToolchainSettingsDetector(System.getenv(),
Map.of("java.home", System.getProperty("java.home", "")),
ToolchainSettingsDetector::readMavenVersion, Files::isDirectory,
System.getProperty("os.name", ""));
}
public AiTestSettings complete(AiTestSettings saved) {
Path jdk = specified(saved.jdk8Home()) ? saved.jdk8Home() : detectJdk8();
Path maven = specified(saved.mavenHome()) ? saved.mavenHome() : detectMaven();
return new AiTestSettings(jdk, maven, saved.codeFreeCommand(), saved.coverageTarget(),
saved.maxRepairRounds(), saved.codeFreeTimeout(), saved.mavenTimeout());
}
private Path detectJdk8() {
Path environmentHome = path(environment.get("JAVA_HOME"));
if (usable(environmentHome)) return normalizeJdk(environmentHome);
Path runtimeHome = normalizeJdk(path(properties.get("java.home")));
if (usable(runtimeHome)) return runtimeHome;
return windows() && usable(WINDOWS_JDK8) ? WINDOWS_JDK8 : Path.of("");
}
private Path detectMaven() {
for (String key : new String[]{"MAVEN_HOME", "M2_HOME"}) {
Path candidate = path(environment.get(key));
if (usable(candidate)) return candidate;
}
Path parsed = parseMavenHome(mavenVersion.get());
if (usable(parsed)) return parsed;
return windows() && usable(WINDOWS_MAVEN) ? WINDOWS_MAVEN : Path.of("");
}
static Path parseMavenHome(String output) {
if (output != null) {
for (String line : output.split("\\R")) {
if (line.regionMatches(true, 0, "Maven home:", 0, "Maven home:".length())) {
return path(line.substring("Maven home:".length()).trim());
}
}
}
return Path.of("");
}
private Path normalizeJdk(Path value) {
if (value != null && value.getFileName() != null
&& "jre".equalsIgnoreCase(value.getFileName().toString())) {
return value.getParent();
}
return value;
}
private boolean usable(Path path) {
return path != null && !path.toString().isBlank() && directoryExists.test(path);
}
private static boolean specified(Path path) {
return path != null && !path.toString().isBlank();
}
private boolean windows() {
return osName.toLowerCase().contains("win");
}
private static Path path(String value) {
return value == null || value.isBlank() ? Path.of("") : Path.of(value.trim());
}
private static String readMavenVersion() {
try {
Process process = new ProcessBuilder("mvn", "-version").redirectErrorStream(true).start();
if (!process.waitFor(5, TimeUnit.SECONDS)) {
process.destroyForcibly();
return "";
}
return new String(process.getInputStream().readAllBytes(), Charset.defaultCharset());
} catch (IOException | InterruptedException ignored) {
if (ignored instanceof InterruptedException) Thread.currentThread().interrupt();
return "";
}
}
}
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -26,11 +27,18 @@ class AiTestSettingsServiceTest {
@Test @Test
void returnsSafeDefaultsWhenNoSettingsExist(@TempDir Path home) throws Exception { void returnsSafeDefaultsWhenNoSettingsExist(@TempDir Path home) throws Exception {
AiTestSettings settings = new AiTestSettingsService(ApplicationPaths.of(home)).load(); ToolchainSettingsDetector detector = new ToolchainSettingsDetector(
Map.of("JAVA_HOME", "C:/jdk8", "MAVEN_HOME", "C:/maven"), Map.of(),
() -> "", ignored -> true, "Windows 11");
AiTestSettingsService service = new AiTestSettingsService(ApplicationPaths.of(home), detector);
AiTestSettings settings = service.load();
assertEquals(85, settings.coverageTarget()); assertEquals(85, settings.coverageTarget());
assertEquals(3, settings.maxRepairRounds()); assertEquals(3, settings.maxRepairRounds());
assertEquals("codefree", settings.codeFreeCommand()); assertEquals("codefree", settings.codeFreeCommand());
assertTrue(settings.jdk8Home().toString().isEmpty()); assertEquals(Path.of("C:/jdk8"), settings.jdk8Home());
assertEquals(Path.of("C:/maven"), settings.mavenHome());
assertTrue(java.nio.file.Files.exists(service.settingsFile()));
} }
} }
@@ -0,0 +1,38 @@
package com.example.workutils.biz.aitest.config;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ToolchainSettingsDetectorTest {
@Test
void discoversJdkRootFromJavaHomeJreAndParsesMavenHome() {
ToolchainSettingsDetector detector = new ToolchainSettingsDetector(Map.of(),
Map.of("java.home", "C:/Program Files/Java/jdk-1.8/jre"),
() -> "Apache Maven 3.8.6\nMaven home: D:/bestpay-maven/apache-maven-3.8.6",
ignored -> true, "Windows 11");
AiTestSettings result = detector.complete(AiTestSettings.defaults());
assertEquals(Path.of("C:/Program Files/Java/jdk-1.8"), result.jdk8Home());
assertEquals(Path.of("D:/bestpay-maven/apache-maven-3.8.6"), result.mavenHome());
}
@Test
void keepsUsableSavedPathsAheadOfDiscoveredValues() {
ToolchainSettingsDetector detector = new ToolchainSettingsDetector(
Map.of("JAVA_HOME", "C:/auto-jdk", "MAVEN_HOME", "C:/auto-maven"),
Map.of(), () -> "", ignored -> true, "Windows 11");
AiTestSettings saved = new AiTestSettings(Path.of("C:/saved-jdk"), Path.of("C:/saved-maven"),
"codefree", 85, 3, java.time.Duration.ofMinutes(5), java.time.Duration.ofMinutes(15));
AiTestSettings result = detector.complete(saved);
assertEquals(saved.jdk8Home(), result.jdk8Home());
assertEquals(saved.mavenHome(), result.mavenHome());
}
}