Skip to content

Commit

Permalink
fix: Introduce descendant process id verification in Camel JBang
Browse files Browse the repository at this point in the history
  • Loading branch information
christophd committed Dec 20, 2024
1 parent 01c4d8a commit e9f6947
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.awaitility.core.ConditionTimeoutException;
import org.citrusframework.exceptions.CitrusRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.awaitility.Awaitility.await;

/**
* Process wrapper also holds the output that has been produced by the completed process.
*/
Expand All @@ -46,8 +46,6 @@ public class ProcessAndOutput {

private BufferedReader reader;

private String app;

ProcessAndOutput(Process process) {
this(process, "");
}
Expand Down Expand Up @@ -146,60 +144,30 @@ private void readChunk() {
* @return
*/
public Long getProcessId() {
return getProcessId(app);
return process.pid();
}

/**
* Get the process id of first descendant or the parent process itself in case there is no descendant process.
* On Linux the shell command represents the parent process and the JBang command as descendant process.
* Get the process descendants.
* On some Linux distributions the shell command represents the parent process and the JBang command as descendant process.
* Typically, we need the JBang command process id.
*
* @return
*/
public Long getProcessId(String app) {

public List<Long> getDescendants() {
try {
if (app != null && isUnix()) {
// wait for descendant process to be available
await().atMost(5000L, TimeUnit.MILLISECONDS)
.until(() -> process.descendants().findAny().isPresent());
return process.descendants()
.filter(p -> p.info().commandLine().orElse("").contains(app))
.findFirst()
.map(ProcessHandle::pid)
.orElse(process.pid());
}

return process.pid();
return process.descendants()
.peek(p -> {
if (LOG.isDebugEnabled()) {
LOG.info(String.format("Found descendant process (pid:%d) for process '%d'", p.pid(), getProcessId()));
}
})
.map(ProcessHandle::pid)
.collect(Collectors.toList());
} catch (ConditionTimeoutException | UnsupportedOperationException | SecurityException e) {
// not able or not allowed to manage descendant process snapshot
// return parent process id as a fallback
return process.pid();
return Collections.emptyList();
}
}

/**
* Get the process id of the parent process.
* Typically, we need the JBang command process id.
*
* @return
*/
public Long getParentProcessId() {
return process.pid();
}


private static boolean isUnix() {
String os = System.getProperty("os.name").toLowerCase();
return os.contains("nix") || os.contains("nux") || os.contains("aix");
}

/**
* Sets the application name that identifies this process.
*
* @param app
*/
public void setApp(String app) {
this.app = app;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.citrusframework.AbstractTestActionBuilder;
import org.citrusframework.actions.AbstractTestAction;
Expand Down Expand Up @@ -83,8 +82,6 @@ public void doExecute(TestContext context) {
.withSystemProperties(systemProperties)
.run(context.replaceDynamicContentInString(scriptOrFile), context.resolveDynamicValuesInList(args));

result.setApp(Objects.requireNonNullElse(app, scriptName));

if (printOutput) {
logger.info("JBang script '%s' output:".formatted(scriptName));
logger.info(result.getOutput());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,10 @@ public void doExecute(TestContext context) {

verifyProcessIsAlive(pao, name);

pao.setApp(integrationToRun.getFileName().toString());
Long pid = pao.getParentProcessId();
Long pid = pao.getProcessId();

context.setVariable(name + ":pid", pid);
context.setVariable(name + ":process:" + pid, pao);
context.setVariable("%s:pid".formatted(name), pid);
context.setVariable("%s:process:%d".formatted(name, pid), pao);

logger.info("Started Camel integration '%s' (%s)".formatted(name, pid));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

package org.citrusframework.camel.actions;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.citrusframework.camel.CamelSettings;
import org.citrusframework.context.TestContext;
Expand Down Expand Up @@ -129,7 +129,7 @@ private Long verifyRouteStatus(String name, String phase, TestContext context) {
return pid;
}

if (context.getVariables().containsKey(name + ":process:" + pid)) {
if (context.getVariables().containsKey("%s:process:%d".formatted(name, pid))) {
// check if process is still alive
ProcessAndOutput pao = context.getVariable(name + ":process:" + pid, ProcessAndOutput.class);
if (!pao.getProcess().isAlive()) {
Expand All @@ -139,14 +139,18 @@ private Long verifyRouteStatus(String name, String phase, TestContext context) {
throw new CitrusRuntimeException(String.format("Failed to verify Camel integration '%s' - exit code %s", name, pao.getProcess().exitValue()));
}

// Verify that current processId is the same as the one saved in test context
Long appPid = pao.getProcessId();
if (!Objects.equals(pid, appPid)) {
// seems like there is another pid (descendant process) that should be verified
if (findProcessAndVerifyStatus(appPid, name, phase)) {
return appPid;
// Check if there is a descendant process for the process saved in test context
List<Long> descendants = pao.getDescendants();
for (Long descendantPid : descendants) {
// seems like there is descendant pid that should be verified
if (findProcessAndVerifyStatus(descendantPid, name, phase)) {
// Update pid in test context so upcoming checks can use the descendant pid
context.setVariable("%s:process:%d".formatted(name, descendantPid), pao);
return descendantPid;
}
}
} else {
logger.warn(String.format("Missing process and output for '%s:process:%d'", name, pid));
}
}

Expand Down Expand Up @@ -177,6 +181,10 @@ private boolean findProcessAndVerifyStatus(Long pid, String name, String phase)
}
}

if (logger.isDebugEnabled()) {
logger.debug(String.format("Camel integration '%s' (pid:%d) not in state '%s'", name, pid, phase));
}

return false;
}

Expand Down

0 comments on commit e9f6947

Please sign in to comment.