Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(test): speed up e2e tests #4302

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading