diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/FSHRunner.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/FSHRunner.java index fc796b7cd..63c69803b 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/FSHRunner.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/FSHRunner.java @@ -1,5 +1,7 @@ package org.hl7.fhir.igtools.publisher; +import com.google.common.collect.ImmutableList; +import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.exec.PumpStreamHandler; @@ -10,12 +12,15 @@ import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.settings.FhirSettings; +import javax.annotation.Nonnull; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.StringJoiner; public class FSHRunner { @@ -64,25 +69,22 @@ protected void runFsh(File file, Publisher.IGBuildMode mode) throws IOException exec.setWorkingDirectory(file); ExecuteWatchdog watchdog = new ExecuteWatchdog(fshTimeout); exec.setWatchdog(watchdog); - - //FIXME This construction is a mess, and should use CommandLine(...).addArgument(...) construction. -Dotasek - String cmd = fshVersion == null ? "sushi" : "npx fsh-sushi@"+fshVersion; - if (mode == Publisher.IGBuildMode.PUBLICATION || mode == Publisher.IGBuildMode.AUTOBUILD) { - cmd += " --require-latest"; - } + try { + final String execString; if (SystemUtils.IS_OS_WINDOWS) { - exec.execute(org.apache.commons.exec.CommandLine.parse("cmd /C "+cmd+" . -o .")); + exec.execute(getWindowsCommandLine(fshVersion, mode)); } else if (FhirSettings.hasNpmPath()) { - ProcessBuilder processBuilder = new ProcessBuilder(new String("bash -c "+cmd)); + ProcessBuilder processBuilder = new ProcessBuilder(new String("bash -c "+ getSushiCommandString(fshVersion,mode))); Map env = processBuilder.environment(); Map vars = new HashMap<>(); vars.putAll(env); String path = FhirSettings.getNpmPath()+":"+env.get("PATH"); vars.put("PATH", path); - exec.execute(org.apache.commons.exec.CommandLine.parse("bash -c "+cmd+" . -o ."), vars); + + exec.execute(getNpmPathCommandLine(fshVersion, mode), vars); } else { - exec.execute(org.apache.commons.exec.CommandLine.parse(cmd+" . -o .")); + exec.execute(getDefaultCommandLine(fshVersion, mode)); } } catch (IOException ioex) { log("Sushi couldn't be run. Complete output from running Sushi : " + pumpHandler.getBufferString()); @@ -99,6 +101,52 @@ protected void runFsh(File file, Publisher.IGBuildMode mode) throws IOException } } + @Nonnull + protected CommandLine getDefaultCommandLine(String fshVersion, Publisher.IGBuildMode mode) { + final List sushiCommandList = getSushiCommandList(fshVersion, mode); + CommandLine commandLine = new CommandLine(sushiCommandList.get(0)); + for (int i = 1; i < sushiCommandList.size(); i++) { + commandLine.addArgument(sushiCommandList.get(i)); + } + commandLine.addArgument(".").addArgument("-o").addArgument("."); + return commandLine; + } + + @Nonnull + protected CommandLine getNpmPathCommandLine(String fshVersion, Publisher.IGBuildMode mode) { + CommandLine commandLine = new CommandLine("bash").addArgument("-c"); + for (String argument : getSushiCommandList(fshVersion,mode)) { + commandLine.addArgument(argument); + } + commandLine.addArgument(".").addArgument("-o").addArgument("."); + return commandLine; + } + + @Nonnull + protected CommandLine getWindowsCommandLine(String fshVersion, Publisher.IGBuildMode mode) { + CommandLine commandLine = new CommandLine("cmd").addArgument("/C"); + for (String argument : getSushiCommandList(fshVersion,mode)) { + commandLine.addArgument(argument); + } + commandLine.addArgument(".").addArgument("-o").addArgument("."); + return commandLine; + } + + protected List getSushiCommandList(String fshVersion, Publisher.IGBuildMode mode) { + final List cmd = fshVersion == null ? List.of("sushi"): List.of("npx", "fsh-sushi@"+fshVersion); + if (mode == Publisher.IGBuildMode.PUBLICATION || mode == Publisher.IGBuildMode.AUTOBUILD) { + return new ImmutableList.Builder().addAll(cmd).add("--require-latest").build(); + } + return cmd; + } + protected String getSushiCommandString(String fshVersion, Publisher.IGBuildMode mode) { + + StringJoiner stringJoiner = new StringJoiner(" "); + for (String argument : getSushiCommandList(fshVersion,mode)) { + stringJoiner.add(argument); + } + return stringJoiner.toString(); + } public class MySushiHandler extends OutputStream { private byte[] buffer; diff --git a/org.hl7.fhir.publisher.core/src/test/java/org/hl7/fhir/igtools/publisher/FSHRunnerTests.java b/org.hl7.fhir.publisher.core/src/test/java/org/hl7/fhir/igtools/publisher/FSHRunnerTests.java new file mode 100644 index 000000000..19f314b63 --- /dev/null +++ b/org.hl7.fhir.publisher.core/src/test/java/org/hl7/fhir/igtools/publisher/FSHRunnerTests.java @@ -0,0 +1,102 @@ +package org.hl7.fhir.igtools.publisher; + +import org.apache.commons.exec.CommandLine; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class FSHRunnerTests { + + + + public static Stream defaultExecStringParams () { + List output = List.of( + Arguments.of(Publisher.IGBuildMode.PUBLICATION, null, "sushi --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, null, "sushi --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.MANUAL, null, "sushi . -o ."), + Arguments.of(Publisher.IGBuildMode.PUBLICATION, "1.2.3", "npx fsh-sushi@1.2.3 --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, "1.2.3", "npx fsh-sushi@1.2.3 --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.MANUAL, "1.2.3", "npx fsh-sushi@1.2.3 . -o .") + ); + return output.stream(); + } + + @ParameterizedTest + @MethodSource("defaultExecStringParams") + public void testDefaultExecString(Publisher.IGBuildMode mode, String fshVersion, String expectedExecString) { + FSHRunner fshRunner = new FSHRunner(Mockito.mock(IWorkerContext.ILoggingService.class)); + final CommandLine actualCommandLine = fshRunner.getDefaultCommandLine(fshVersion, mode); + assertIsEqual(CommandLine.parse(expectedExecString), actualCommandLine); + } + + public static Stream sushiCommandParams () { + List output = List.of( + Arguments.of(Publisher.IGBuildMode.PUBLICATION, null, "sushi --require-latest"), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, null, "sushi --require-latest"), + Arguments.of(Publisher.IGBuildMode.MANUAL, null, "sushi"), + Arguments.of(Publisher.IGBuildMode.PUBLICATION, "1.2.3", "npx fsh-sushi@1.2.3 --require-latest"), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, "1.2.3", "npx fsh-sushi@1.2.3 --require-latest"), + Arguments.of(Publisher.IGBuildMode.MANUAL, "1.2.3", "npx fsh-sushi@1.2.3") + ); + return output.stream(); + } + + @ParameterizedTest + @MethodSource("sushiCommandParams") + public void testGetSushiCommand(Publisher.IGBuildMode mode, String fshVersion, String expectedSushiCommand) { + FSHRunner fshRunner = new FSHRunner(Mockito.mock(IWorkerContext.ILoggingService.class)); + assertEquals(expectedSushiCommand, fshRunner.getSushiCommandString(fshVersion,mode)); + } + + private void assertIsEqual(CommandLine a, CommandLine b) { + assertEquals(a.getExecutable(), b.getExecutable()); + assertArrayEquals(a.getArguments(), b.getArguments()); + } + + public static Stream windowsExecStringParams () { + List output = List.of( + Arguments.of(Publisher.IGBuildMode.PUBLICATION, null, "cmd /C sushi --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, null, "cmd /C sushi --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.MANUAL, null, "cmd /C sushi . -o ."), + Arguments.of(Publisher.IGBuildMode.PUBLICATION, "1.2.3", "cmd /C npx fsh-sushi@1.2.3 --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, "1.2.3", "cmd /C npx fsh-sushi@1.2.3 --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.MANUAL, "1.2.3", "cmd /C npx fsh-sushi@1.2.3 . -o .") + ); + return output.stream(); + } + @ParameterizedTest + @MethodSource("windowsExecStringParams") + public void testWindowsExecString(Publisher.IGBuildMode mode, String fshVersion, String expectedExecString) { + FSHRunner fshRunner = new FSHRunner(Mockito.mock(IWorkerContext.ILoggingService.class)); + final CommandLine actualCommandLine = fshRunner.getWindowsCommandLine(fshVersion, mode); + assertIsEqual(CommandLine.parse(expectedExecString), actualCommandLine); + } + + public static Stream npmPathExecStringParams () { + List output = List.of( + Arguments.of(Publisher.IGBuildMode.PUBLICATION, null, "bash -c sushi --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, null, "bash -c sushi --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.MANUAL, null, "bash -c sushi . -o ."), + Arguments.of(Publisher.IGBuildMode.PUBLICATION, "1.2.3", "bash -c npx fsh-sushi@1.2.3 --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.AUTOBUILD, "1.2.3", "bash -c npx fsh-sushi@1.2.3 --require-latest . -o ."), + Arguments.of(Publisher.IGBuildMode.MANUAL, "1.2.3", "bash -c npx fsh-sushi@1.2.3 . -o .") + ); + return output.stream(); + } + @ParameterizedTest + @MethodSource("npmPathExecStringParams") + public void testNpmPathExecString(Publisher.IGBuildMode mode, String fshVersion, String expectedExecString) { + FSHRunner fshRunner = new FSHRunner(Mockito.mock(IWorkerContext.ILoggingService.class)); + final CommandLine actualCommandLine = fshRunner.getNpmPathCommandLine(fshVersion, mode); + assertIsEqual(CommandLine.parse(expectedExecString), actualCommandLine); + } +} diff --git a/pom.xml b/pom.xml index 41aeda6e8..de16c68f9 100644 --- a/pom.xml +++ b/pom.xml @@ -275,6 +275,12 @@ test + + org.mockito + mockito-core + 4.8.1 + test + net.sourceforge.plantuml