feat: discover default AI test toolchain
This commit is contained in:
@@ -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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user