Skip to content

Commit

Permalink
#4: Fix progress bar and console reporting.
Browse files Browse the repository at this point in the history
The RemoteTestResultReporter is unsuitable for a "live update" of test state. Switching to a test listener, however, brings the desired behaviour.

To get the missing time logging in the console logging, corresponding files and mappings have to be added.

There is still an open issue with the logging reporter; in the container, the PERFORMANCE shows "0ms", although each job shows its execution time.
  • Loading branch information
turing85 committed Sep 6, 2024
1 parent c08d4e7 commit cdb7a1c
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 182 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
CITRUS_REMOTE_LOG_ARTIFACT=citrus-remote-logs

JAR_ARTIFACT_NAME=jars

JAVA_DISTRIBUTION=temurin
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ on:
- 'LICENSE'
- 'NOTICE'
env:
CITRUS_REMOTE_LOGS_ARTIFACT: will-be-read-from-env-file
JAVA_DISTRIBUTION: will-be-read-from-env-file
JAVA_VERSION: will-be-read-from-env-file
JAR_ARTIFACT_NAME: will-be-read-from-env-file
Expand Down Expand Up @@ -80,6 +81,16 @@ jobs:
--no-transfer-progress \
verify
- name: Print Citrus remote Logs
if: ${{ always() }}
run: |
if [[ -f citrus-remote-sample/target/container-logs/citrus.log ]]
then
cat citrus-remote-sample/target/container-logs/citrus.log
else
echo "(log file 'citrus-remote-sample/target/container-logs/citrus.log' not found)"
fi
- name: Upload JARs
uses: actions/upload-artifact@v4
if: ${{ always() }}
Expand All @@ -101,6 +112,15 @@ jobs:
**/target/citrus-remote/junitreports/TEST*.xml
retention-days: 2

- name: Upload Citrus remote log
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
if-no-files-found: error
name: ${{ env.CITRUS_REMOTE_LOGS_ARTIFACT }}
path: citrus-remote-sample/target/container-logs/citrus.log
retention-days: 2

- name: Get PR number
id: get-pr-number
if: ${{ always() && github.event_name == 'pull_request' }}
Expand Down
2 changes: 1 addition & 1 deletion citrus-remote-sample/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
<run>
<engine>testng</engine>
<async>true</async>
<pollingInterval>15000</pollingInterval>
<pollingInterval>1000</pollingInterval>
</run>
<report>
<directory>${citrus.remote.report.directory}</directory>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@
import org.citrusframework.testng.spring.TestNGCitrusSpringSupport;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Optional;
import org.testng.annotations.Test;

import static org.citrusframework.actions.SleepAction.Builder.sleep;
import static org.citrusframework.container.Async.Builder.async;
import static org.citrusframework.http.actions.HttpActionBuilder.http;

public class GetTextIT extends TestNGCitrusSpringSupport {
@Test
@DataProvider
public Object[][] body() {
return new Object[][]{
{null, "foo"},
{null, "bar"},
{null, "citrus:randomString(10, MIXED, true)"}};
}

@Test(dataProvider = "body")
@CitrusTest
public void test(@Optional @CitrusResource TestCaseRunner runner) {
runner.variable("body", "citrus:randomString(10, MIXED, true)");
public void test(@Optional @CitrusResource TestCaseRunner runner, String body) {
runner.variable("body", body);
runner.given(async().actions(
http().server("httpServer")
.receive()
Expand Down Expand Up @@ -44,5 +54,7 @@ public void test(@Optional @CitrusResource TestCaseRunner runner) {
.message()
.contentType(MediaType.TEXT_PLAIN_VALUE)
.body("${body}"));

runner.$(sleep().seconds(2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RequestBody;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
Expand All @@ -32,10 +33,9 @@
import org.citrusframework.TestClass;
import org.citrusframework.main.CitrusAppConfiguration;
import org.citrusframework.main.TestRunConfiguration;
import org.citrusframework.remote.controller.RunController;
import org.citrusframework.remote.job.RunJob;
import org.citrusframework.remote.model.RemoteResult;
import org.citrusframework.remote.reporter.RemoteTestResultReporter;
import org.citrusframework.remote.listener.RemoteTestListener;
import org.citrusframework.remote.transformer.JsonRequestTransformer;
import org.citrusframework.remote.transformer.JsonResponseTransformer;
import org.citrusframework.report.JUnitReporter;
Expand All @@ -48,7 +48,6 @@
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -84,8 +83,8 @@ public class CitrusRemoteApplication extends AbstractVerticle {
private Future<List<RemoteResult>> remoteResultFuture;

/** Latest test reports */
private final RemoteTestResultReporter remoteTestResultReporter =
new RemoteTestResultReporter();
private final RemoteTestListener remoteTestListener =
new RemoteTestListener();

/** Router customizations */
private final List<Consumer<Router>> routerCustomizations;
Expand All @@ -111,16 +110,13 @@ public CitrusRemoteApplication(
@Override
public void start() {
CitrusInstanceManager.mode(CitrusInstanceStrategy.SINGLETON);
CitrusInstanceManager.addInstanceProcessor(citrus ->
citrus.addTestReporter(remoteTestResultReporter));
CitrusInstanceManager
.addInstanceProcessor(citrus -> citrus.addTestListener(remoteTestListener));

Router router = Router.router(getVertx());
router.route().handler(BodyHandler.create());
router.route().handler(ctx -> {
logger.info(
"{} {}",
ctx.request().method(),
ctx.request().uri());
logger.info("{} {}", ctx.request().method(), ctx.request().uri());
ctx.next();
});
addHealthEndpoint(router);
Expand All @@ -140,7 +136,8 @@ public void start() {
private static void addHealthEndpoint(Router router) {
router.get("/health")
.handler(wrapThrowingHandler(ctx ->
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
ctx.response()
.putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.end("{ \"status\": \"UP\" }")));
}

Expand Down Expand Up @@ -178,17 +175,17 @@ private void addResultsEndpoints(Router router) {
response.end(responseTransformer.render(results)))
.onFailure(throwable -> response
.setStatusCode(HttpResponseStatus.PARTIAL_CONTENT.code())
.end(responseTransformer.render(Collections.emptyList())));
.end(responseTransformer
.render(remoteTestListener.toRemoteResults())));
} else {
final List<RemoteResult> results = new ArrayList<>();
remoteTestResultReporter.getLatestResults().doWithResults(result ->
results.add(RemoteResult.fromTestResult(result)));
final List<RemoteResult> results = remoteTestListener.toRemoteResults();
logger.info("results = {}", results.size());
response.end(responseTransformer.render(results));
}
}));
router.get("/results")
.handler(ctx -> ctx.response().end(
responseTransformer.render(remoteTestResultReporter.getTestReport())));
.handler(ctx -> ctx.response()
.end(responseTransformer.render(remoteTestListener.generateTestReport())));
router.get("/results/files")
.handler(wrapThrowingHandler(ctx -> {
File junitReportsFolder = new File(getJUnitReportsFolder());
Expand Down Expand Up @@ -239,31 +236,37 @@ private void addResultsEndpoints(Router router) {

private void addRunEndpoints(Router router) {
router.get("/run")
.handler(wrapThrowingHandler(ctx -> {
TestRunConfiguration runConfiguration = constructRunConfig(ctx);
runTestsAsync(runConfiguration, ctx.response());
}));
.handler(wrapThrowingHandler(ctx ->
runTestsAsync(constructRunConfig(ctx.request().params()), ctx.response())));
router.post("/run")
.handler(wrapThrowingHandler(ctx ->
runTestsAsync(constructRunConfig(ctx.body()), ctx.response())));
router.put("/run")
.handler(wrapThrowingHandler(ctx -> {
remoteResultFuture = Future.fromCompletionStage(CompletableFuture.supplyAsync(
constructTestRun(ctx)::call,
executorService));
remoteTestListener.reset();
remoteResultFuture = startTestAsync(constructRunConfig(ctx.body()));
ctx.response().end("");
}));
router.post("/run")
.handler(wrapThrowingHandler(ctx -> {
HttpServerResponse response = ctx.response();
TestRunConfiguration runConfiguration = requestTransformer.read(
ctx.body().asString(),
TestRunConfiguration.class);
runTestsAsync(runConfiguration, response);
}));
}

private TestRunConfiguration constructRunConfig(RoutingContext ctx)
public static Handler<RoutingContext> wrapThrowingHandler(
ThrowingHandler<RoutingContext> handler) {
return ctx -> {
try {
handler.handle(ctx);
} catch (Exception e) {
ctx.response()
.setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code())
.end(e.getMessage());
}
};
}



private TestRunConfiguration constructRunConfig(MultiMap queryParams)
throws UnsupportedEncodingException {
TestRunConfiguration runConfiguration = new TestRunConfiguration();
MultiMap queryParams = ctx.request().params();
if (queryParams.contains("engine")) {
String engine = queryParams.get("engine");
runConfiguration.setEngine(URLDecoder.decode(engine, ENCODING));
Expand Down Expand Up @@ -291,12 +294,16 @@ private TestRunConfiguration constructRunConfig(RoutingContext ctx)
return runConfiguration;
}

private TestRunConfiguration constructRunConfig(RequestBody body) {
return requestTransformer.read(body.asString(), TestRunConfiguration.class);
}

private void runTestsAsync(
TestRunConfiguration runConfiguration,
HttpServerResponse response) {
Future
.fromCompletionStage(CompletableFuture.supplyAsync(
() -> runTests(runConfiguration),
new RunJob(configuration, runConfiguration, remoteTestListener),
executorService))
.onSuccess(results ->
response.end(responseTransformer.render(results)))
Expand All @@ -305,16 +312,10 @@ private void runTestsAsync(
.end(error.getMessage()));
}

private RunJob constructTestRun(RoutingContext ctx) {
TestRunConfiguration config = requestTransformer.read(
ctx.body().asString(),
TestRunConfiguration.class);
return new RunJob(config) {
@Override
public List<RemoteResult> run(TestRunConfiguration runConfiguration) {
return runTests(runConfiguration);
}
};
private Future<List<RemoteResult>> startTestAsync(TestRunConfiguration testRunConfiguration) {
return Future.fromCompletionStage(CompletableFuture.supplyAsync(
new RunJob(configuration, testRunConfiguration, remoteTestListener),
executorService));
}

private void addConfigEndpoints(Router router) {
Expand All @@ -330,61 +331,18 @@ private void addConfigEndpoints(Router router) {
CitrusAppConfiguration.class))));
}

public static Handler<RoutingContext> wrapThrowingHandler(
ThrowingHandler<RoutingContext> handler) {
return ctx -> {
try {
handler.handle(ctx);
} catch (Exception e) {
ctx.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code())
.end(e.getMessage());
}
};
}

/**
* Construct run controller and execute with given configuration.
* @param runConfiguration
* @return remote results
*/
private List<RemoteResult> runTests(TestRunConfiguration runConfiguration) {
RunController runController = new RunController(configuration);

runController.setEngine(runConfiguration.getEngine());
runController.setIncludes(runConfiguration.getIncludes());

if (!runConfiguration.getDefaultProperties().isEmpty()) {
runController.addDefaultProperties(runConfiguration.getDefaultProperties());
}

if (runConfiguration.getPackages().isEmpty() && runConfiguration.getTestSources().isEmpty()) {
runController.runAll();
}

if (!runConfiguration.getPackages().isEmpty()) {
runController.runPackages(runConfiguration.getPackages());
}

if (!runConfiguration.getTestSources().isEmpty()) {
runController.runClasses(runConfiguration.getTestSources());
}

List<RemoteResult> results = new ArrayList<>();
remoteTestResultReporter.getLatestResults().doWithResults(result -> results.add(RemoteResult.fromTestResult(result)));
return results;
}

/**
* Find reports folder based in unit testing framework present on classpath.
* @return
*/
private String getJUnitReportsFolder() {

if (isPresent("org.testng.annotations.Test")) {
return "test-output" + File.separator + "junitreports";
} else if (isPresent("org.junit.Test")) {
JUnitReporter jUnitReporter = new JUnitReporter();
return jUnitReporter.getReportDirectory() + File.separator + jUnitReporter.getOutputDirectory();
return jUnitReporter.getReportDirectory() +
File.separator +
jUnitReporter.getOutputDirectory();
} else {
return new LoggingReporter().getReportDirectory();
}
Expand Down
Loading

0 comments on commit cdb7a1c

Please sign in to comment.