Skip to content

Commit

Permalink
refactor(test): speed up e2e tests (#4302)
Browse files Browse the repository at this point in the history
  • Loading branch information
ndr-brt authored Jun 25, 2024
1 parent cbe3225 commit e60d30b
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 261 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.junit.extensions;

import org.eclipse.edc.junit.testfixtures.TestUtils;
import org.eclipse.edc.spi.EdcException;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Stream;

/**
* Read the classpath entries of the gradle modules.
*/
public class ClasspathReader {

private static final File PROJECT_ROOT = TestUtils.findBuildRoot();
private static final File GRADLE_WRAPPER = new File(PROJECT_ROOT, TestUtils.GRADLE_WRAPPER);

/**
* Get classpath entries for the passed modules
*
* @param modules the modules.
* @return the classpath entries.
*/
public static URL[] classpathFor(String... modules) {
try {
// Run a Gradle custom task to determine the runtime classpath of the module to run
var printClasspath = Arrays.stream(modules).map(it -> it + ":printClasspath");
var commandStream = Stream.of(GRADLE_WRAPPER.getCanonicalPath(), "-q");
var command = Stream.concat(commandStream, printClasspath).toArray(String[]::new);

var exec = Runtime.getRuntime().exec(command);

try (var reader = exec.inputReader()) {
return reader.lines().map(line -> line.split(":|\\s"))
.flatMap(Arrays::stream)
.filter(s -> !s.isBlank())
.map(File::new)
.flatMap(ClasspathReader::resolveClasspathEntry)
.toArray(URL[]::new);
}

} catch (IOException e) {
throw new EdcException(e);
}
}

/**
* Replace Gradle subproject JAR entries with subproject build directories in classpath. This ensures modified
* classes are picked up without needing to rebuild dependent JARs.
*
* @param classpathFile classpath entry file.
* @return resolved classpath entries for the input argument.
*/
private static Stream<URL> resolveClasspathEntry(File classpathFile) {
try {
if (isJar(classpathFile) && isUnderRoot(classpathFile)) {
var buildDir = classpathFile.getCanonicalFile().getParentFile().getParentFile();
return Stream.of(
new File(buildDir, "/classes/java/main").toURI().toURL(),
new File(buildDir, "../src/main/resources").toURI().toURL()
);
} else {
var sanitizedClassPathEntry = classpathFile.getCanonicalPath().replace("build/resources/main", "src/main/resources");
return Stream.of(new File(sanitizedClassPathEntry).toURI().toURL());
}

} catch (IOException e) {
throw new EdcException(e);
}
}

private static boolean isUnderRoot(File classPathFile) throws IOException {
return classPathFile.getCanonicalPath().startsWith(ClasspathReader.PROJECT_ROOT.getCanonicalPath() + File.separator);
}

private static boolean isJar(File classPathFile) throws IOException {
return classPathFile.getCanonicalPath().toLowerCase(Locale.ROOT).endsWith(".jar");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package org.eclipse.edc.junit.extensions;

import org.eclipse.edc.boot.system.runtime.BaseRuntime;
import org.eclipse.edc.junit.testfixtures.TestUtils;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.monitor.ConsoleMonitor;
import org.eclipse.edc.spi.monitor.Monitor;
Expand All @@ -24,22 +23,16 @@
import org.eclipse.edc.spi.system.configuration.Config;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.eclipse.edc.boot.system.ExtensionLoader.loadMonitor;

Expand All @@ -52,52 +45,31 @@ public class EmbeddedRuntime extends BaseRuntime {

private final String name;
private final Map<String, String> properties;
private final String[] additionalModules;
private final LinkedHashMap<Class<?>, Object> serviceMocks = new LinkedHashMap<>();
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final MultiSourceServiceLocator serviceLocator;
private final URL[] classPathEntries;

public EmbeddedRuntime(String name, Map<String, String> properties, String... additionalModules) {
this(new MultiSourceServiceLocator(), name, properties, additionalModules);
this(new MultiSourceServiceLocator(), name, properties, ClasspathReader.classpathFor(additionalModules));
}

private EmbeddedRuntime(MultiSourceServiceLocator serviceLocator, String name, Map<String, String> properties, String... additionalModules) {
public EmbeddedRuntime(String name, Map<String, String> properties, URL[] classpathEntries) {
this(new MultiSourceServiceLocator(), name, properties, classpathEntries);
}

private EmbeddedRuntime(MultiSourceServiceLocator serviceLocator, String name, Map<String, String> properties, URL[] classPathEntries) {
super(serviceLocator);
this.serviceLocator = serviceLocator;
this.name = name;
this.properties = properties;
this.additionalModules = additionalModules;
this.classPathEntries = classPathEntries;
}

@Override
public void boot(boolean addShutdownHook) {
try {
// Find the project root directory, moving up the directory tree
var root = TestUtils.findBuildRoot();

// Run a Gradle custom task to determine the runtime classpath of the module to run
var printClasspath = Arrays.stream(additionalModules).map(it -> it + ":printClasspath");
var commandStream = Stream.of(new File(root, TestUtils.GRADLE_WRAPPER).getCanonicalPath(), "-q");
var command = Stream.concat(commandStream, printClasspath).toArray(String[]::new);

var exec = Runtime.getRuntime().exec(command);
var classpathString = new String(exec.getInputStream().readAllBytes());
var errorOutput = new String(exec.getErrorStream().readAllBytes());
if (exec.waitFor() != 0) {
throw new EdcException(format("Failed to run gradle command: [%s]. Output: %s %s",
String.join(" ", command), classpathString, errorOutput));
}

// Replace subproject JAR entries with subproject build directories in classpath.
// This ensures modified classes are picked up without needing to rebuild dependent JARs.
var classPathEntries = Arrays.stream(classpathString.split(":|\\s"))
.filter(s -> !s.isBlank())
.flatMap(p -> resolveClassPathEntry(root, p))
.toArray(URL[]::new);

// Create a ClassLoader that only has the target module class path, and is not
// parented with the current ClassLoader.
var classLoader = URLClassLoader.newInstance(classPathEntries);
MONITOR.info("Starting runtime %s".formatted(name));

// Temporarily inject system properties.
var savedProperties = (Properties) System.getProperties().clone();
Expand All @@ -106,10 +78,10 @@ public void boot(boolean addShutdownHook) {
var runtimeException = new AtomicReference<Exception>();
var latch = new CountDownLatch(1);

MONITOR.info("Starting runtime %s with additional modules: [%s]".formatted(name, String.join(",", additionalModules)));

executorService.execute(() -> {
try {
var classLoader = URLClassLoader.newInstance(classPathEntries);

Thread.currentThread().setContextClassLoader(classLoader);

super.boot(false);
Expand Down Expand Up @@ -173,34 +145,4 @@ public <T> T getService(Class<T> clazz) {
public ServiceExtensionContext getContext() {
return context;
}

/**
* Replace Gradle subproject JAR entries with subproject build directories in classpath. This ensures modified
* classes are picked up without needing to rebuild dependent JARs.
*
* @param root project root directory.
* @param classPathEntry class path entry to resolve.
* @return resolved class path entries for the input argument.
*/
private Stream<URL> resolveClassPathEntry(File root, String classPathEntry) {
try {
var f = new File(classPathEntry).getCanonicalFile();

// If class path entry is not a JAR under the root (i.e. a sub-project), do not transform it
var isUnderRoot = f.getCanonicalPath().startsWith(root.getCanonicalPath() + File.separator);
if (!classPathEntry.toLowerCase(Locale.ROOT).endsWith(".jar") || !isUnderRoot) {
var sanitizedClassPathEntry = classPathEntry.replace("build/resources/main", "src/main/resources");
return Stream.of(new File(sanitizedClassPathEntry).toURI().toURL());
}

// Replace JAR entry with the resolved classes and resources folder
var buildDir = f.getParentFile().getParentFile();
return Stream.of(
new File(buildDir, "/classes/java/main").toURI().toURL(),
new File(buildDir, "../src/main/resources").toURI().toURL()
);
} catch (IOException e) {
throw new EdcException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package org.eclipse.edc.connector.controlplane.test.system.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.restassured.specification.RequestSpecification;
import jakarta.json.Json;
Expand Down Expand Up @@ -243,7 +244,6 @@ public JsonArray getCatalogDatasets(Participant provider, JsonObject querySpec)
* @return dataset.
*/
public JsonObject getDatasetForAsset(Participant provider, String assetId) {
var datasetReference = new AtomicReference<JsonObject>();
var requestBody = createObjectBuilder()
.add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE))
.add(TYPE, "DatasetRequest")
Expand All @@ -253,25 +253,24 @@ public JsonObject getDatasetForAsset(Participant provider, String assetId) {
.add("protocol", protocol)
.build();

await().atMost(timeout).untilAsserted(() -> {
var response = managementEndpoint.baseRequest()
.contentType(JSON)
.when()
.body(requestBody)
.post("/v3/catalog/dataset/request")
.then()
.log().ifError()
.statusCode(200)
.extract().body().asString();

var compacted = objectMapper.readValue(response, JsonObject.class);

var dataset = jsonLd.expand(compacted).orElseThrow(f -> new EdcException(f.getFailureDetail()));

datasetReference.set(dataset);
});

return datasetReference.get();
var response = managementEndpoint.baseRequest()
.contentType(JSON)
.when()
.body(requestBody)
.post("/v3/catalog/dataset/request")
.then()
.statusCode(200)
.contentType(JSON)
.log().ifValidationFails()
.extract();

try {
var responseBody = response.body().asString();
var compacted = objectMapper.readValue(responseBody, JsonObject.class);
return jsonLd.expand(compacted).orElseThrow(f -> new EdcException(f.getFailureDetail()));
} catch (JsonProcessingException e) {
throw new EdcException("Cannot deserialize dataset", e);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,4 @@ dependencies {
testImplementation(project(":core:common:junit"))
}

edcBuild {
swagger {
apiGroup.set("control-api")
}
}



11 changes: 9 additions & 2 deletions system-tests/e2e-transfer-test/control-plane/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,22 @@ plugins {
}

dependencies {
implementation(project(":core:common:edr-store-core"))
implementation(project(":core:common:token-core"))
implementation(project(":core:control-plane:control-plane-core"))
implementation(project(":data-protocols:dsp"))
implementation(project(":extensions:common:http"))
implementation(project(":extensions:common:api:control-api-configuration"))
implementation(project(":extensions:common:api:management-api-configuration"))
implementation(project(":extensions:common:iam:iam-mock"))

implementation(project(":extensions:control-plane:api:control-plane-api"))
implementation(project(":extensions:control-plane:api:management-api"))
implementation(project(":extensions:common:api:control-api-configuration"))
implementation(project(":extensions:common:api:management-api-configuration"))
implementation(project(":extensions:control-plane:api:management-api:edr-cache-api"))
implementation(project(":extensions:control-plane:callback:callback-event-dispatcher"))
implementation(project(":extensions:control-plane:callback:callback-http-dispatcher"))
implementation(project(":extensions:control-plane:edr:edr-store-receiver"))
implementation(project(":extensions:control-plane:transfer:transfer-data-plane-signaling"))

implementation(project(":core:data-plane-selector:data-plane-selector-core"))
implementation(project(":extensions:data-plane-selector:data-plane-selector-api"))
Expand Down
1 change: 1 addition & 0 deletions system-tests/e2e-transfer-test/data-plane/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
implementation(project(":extensions:data-plane:data-plane-kafka"))
implementation(project(":extensions:data-plane:data-plane-http-oauth2"))
implementation(project(":extensions:data-plane:data-plane-control-api"))
implementation(project(":extensions:data-plane:data-plane-public-api-v2"))
implementation(project(":extensions:data-plane:data-plane-signaling:data-plane-signaling-api"))
}

Expand Down
Loading

0 comments on commit e60d30b

Please sign in to comment.