From 98181e2e6337ed6cd9df3fac758d60fd83b63183 Mon Sep 17 00:00:00 2001 From: Atharva Patil Date: Tue, 4 Apr 2023 00:07:12 -0700 Subject: [PATCH 1/6] Implementation + testing for CLI options --json and --json-file. --- build.gradle | 4 +- gradle.properties | 3 +- .../src/org/lflang/tests/cli/LfcCliTest.java | 112 +++++++++++--- org.lflang/src/org/lflang/cli/CliBase.java | 142 ++++++++++++++++-- org.lflang/src/org/lflang/cli/Lfc.java | 2 +- org.lflang/src/org/lflang/cli/Lff.java | 2 +- 6 files changed, 227 insertions(+), 38 deletions(-) diff --git a/build.gradle b/build.gradle index 96f973aadd..6a98ba7165 100644 --- a/build.gradle +++ b/build.gradle @@ -42,8 +42,8 @@ subprojects { implementation group: 'com.google.inject', name: 'guice', version: guiceVersion // https://picocli.info/ implementation group: 'info.picocli', name: 'picocli', version: picocliVersion - } - dependencies { + // https://mvnrepository.com/artifact/com.google.code.gson/gson + implementation group: 'com.google.code.gson', name: 'gson', version: gsonVersion implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlinVersion implementation group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion } diff --git a/gradle.properties b/gradle.properties index fe695c4c7f..4b1302bdb8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,9 +3,9 @@ group=org.lflang version=0.4.1-SNAPSHOT [versions] -picocliVersion=4.7.0 googleJavaFormatVersion=1.15.0 guiceVersion=5.1.0 +gsonVersion=2.10.1 jacocoVersion=0.8.7 jupiterVersion=5.8.2 jUnitPlatformVersion=1.8.2 @@ -15,6 +15,7 @@ kotlinVersion=1.6.20 lsp4jVersion=0.14.0 mwe2LaunchVersion=2.12.2 openTest4jVersion=1.2.0 +picocliVersion=4.7.0 resourcesVersion=3.16.0 shadowJarVersion=7.1.2 spotlessVersion=6.11.0 diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java index f3abd9d869..99fb5ba3ce 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -62,6 +62,29 @@ public class LfcCliTest { } """; + static final String JSON_STRING = """ + { + "src": "src/File.lf", + "out": "src", + "properties": { + "build-type": "Release", + "clean": true, + "target-compiler": "gcc", + "external-runtime-path": "src", + "federated": true, + "logging": "info", + "lint": true, + "no-compile": true, + "quiet": true, + "rti": "path/to/rti", + "runtime-version": "rs", + "scheduler": "GEDF_NP", + "threading": false, + "workers": "1" + } + } + """; + @Test public void testHelpArg() { lfcTester.run("--help", "--version") @@ -72,6 +95,30 @@ public void testHelpArg() { }); } + @Test + public void testMutuallyExclusiveCliArgs() { + lfcTester.run("File.lf", "--json", JSON_STRING) + .verify(result -> { + result.checkStdErr(containsString( + "are mutually exclusive (specify only one)")); + result.checkFailed(); + }); + + lfcTester.run("File.lf", "--json-file", "test.json") + .verify(result -> { + result.checkStdErr(containsString( + "are mutually exclusive (specify only one)")); + result.checkFailed(); + }); + + lfcTester.run("--json", JSON_STRING, "--json-file", "test.json") + .verify(result -> { + result.checkStdErr(containsString( + "are mutually exclusive (specify only one)")); + result.checkFailed(); + }); + } + @Test public void testVersion() { lfcTester.run("--version") @@ -163,6 +210,29 @@ public void testGenInSrcDir(@TempDir Path tempDir) throws IOException { }); } + public void verifyGeneratorArgs(Path tempDir, String[] args) { + LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); + + fixture.run(tempDir, args) + .verify(result -> { + // Don't validate execution because args are dummy args. + Properties properties = fixture.lfc.getGeneratorArgs(); + assertEquals(properties.getProperty(BuildParm.BUILD_TYPE.getKey()), "Release"); + assertEquals(properties.getProperty(BuildParm.CLEAN.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.TARGET_COMPILER.getKey()), "gcc"); + assertEquals(properties.getProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey()), "src"); + assertEquals(properties.getProperty(BuildParm.LOGGING.getKey()), "info"); + assertEquals(properties.getProperty(BuildParm.LINT.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.NO_COMPILE.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.QUIET.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.RTI.getKey()), "path/to/rti"); + assertEquals(properties.getProperty(BuildParm.RUNTIME_VERSION.getKey()), "rs"); + assertEquals(properties.getProperty(BuildParm.SCHEDULER.getKey()), "GEDF_NP"); + assertEquals(properties.getProperty(BuildParm.THREADING.getKey()), "false"); + assertEquals(properties.getProperty(BuildParm.WORKERS.getKey()), "1"); + }); + } + @Test public void testGeneratorArgs(@TempDir Path tempDir) throws IOException { @@ -188,26 +258,30 @@ public void testGeneratorArgs(@TempDir Path tempDir) "--threading", "false", "--workers", "1", }; - LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); + verifyGeneratorArgs(tempDir, args); + } - fixture.run(tempDir, args) - .verify(result -> { - // Don't validate execution because args are dummy args. - Properties properties = fixture.lfc.getGeneratorArgs(); - assertEquals(properties.getProperty(BuildParm.BUILD_TYPE.getKey()), "Release"); - assertEquals(properties.getProperty(BuildParm.CLEAN.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.TARGET_COMPILER.getKey()), "gcc"); - assertEquals(properties.getProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey()), "src"); - assertEquals(properties.getProperty(BuildParm.LOGGING.getKey()), "info"); - assertEquals(properties.getProperty(BuildParm.LINT.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.NO_COMPILE.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.QUIET.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.RTI.getKey()), "path/to/rti"); - assertEquals(properties.getProperty(BuildParm.RUNTIME_VERSION.getKey()), "rs"); - assertEquals(properties.getProperty(BuildParm.SCHEDULER.getKey()), "GEDF_NP"); - assertEquals(properties.getProperty(BuildParm.THREADING.getKey()), "false"); - assertEquals(properties.getProperty(BuildParm.WORKERS.getKey()), "1"); - }); + @Test + public void testGeneratorArgsJsonString(@TempDir Path tempDir) + throws IOException { + TempDirBuilder dir = dirBuilder(tempDir); + dir.file("src/File.lf", LF_PYTHON_FILE); + dir.mkdirs("path//to/rti"); + + String[] args = {"--json", JSON_STRING}; + verifyGeneratorArgs(tempDir, args); + } + + @Test + public void testGeneratorArgsJsonFile(@TempDir Path tempDir) + throws IOException { + TempDirBuilder dir = dirBuilder(tempDir); + dir.file("src/File.lf", LF_PYTHON_FILE); + dir.file("src/test.json", JSON_STRING); + dir.mkdirs("path//to/rti"); + + String[] args = {"--json-file", "src/test.json"}; + verifyGeneratorArgs(tempDir, args); } static class LfcTestFixture extends CliToolTestFixture { diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index b825d91966..0f235e9039 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -4,12 +4,22 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; import java.util.stream.Collectors; import picocli.CommandLine; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; +import picocli.CommandLine.Spec; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; @@ -23,6 +33,10 @@ import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; import org.lflang.util.FileUtil; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonParseException; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; @@ -36,22 +50,42 @@ * @author Atharva Patil */ public abstract class CliBase implements Runnable { + /** + * Models a command specification, including the options, positional + * parameters and subcommands supported by the command. + */ + @Spec CommandSpec spec; /** * Options and parameters present in both Lfc and Lff. */ - @Parameters( - arity = "1..", - paramLabel = "FILES", - description = "Paths of the files to run Lingua Franca programs on.") - protected List files; + static class MutuallyExclusive { + @Parameters( + arity = "1..", + paramLabel = "FILES", + description = "Paths of the files to run Lingua Franca programs on.") + protected List files; + + @Option( + names="--json", + description="JSON object containing CLI arguments.") + private String jsonString; + + @Option( + names="--json-file", + description="JSON file containing CLI arguments.") + private Path jsonFile; + } - @Option( - names = {"-o", "--output-path"}, - defaultValue = "", - fallbackValue = "", - description = "Specify the root output directory.") - private Path outputPath; + @ArgGroup(exclusive = true, multiplicity = "1") + MutuallyExclusive topLevelArg; + + @Option( + names = {"-o", "--output-path"}, + defaultValue = "", + fallbackValue = "", + description = "Specify the root output directory.") + private Path outputPath; /** * Used to collect all errors that happen during validation/generation. @@ -111,9 +145,39 @@ public void doExecute(Io io, String[] args) { /** * The entrypoint of Picocli applications - the first method called when * CliBase, which implements the Runnable interface, is instantiated. - * Lfc and Lff have their own specific implementations for this method. */ - public abstract void run(); + public void run() { + // If args are given in a json file, store its contents in jsonString. + if (topLevelArg.jsonFile != null) { + try { + topLevelArg.jsonString = new String(Files.readAllBytes( + io.getWd().resolve(topLevelArg.jsonFile))); + } catch (IOException e) { + reporter.printFatalErrorAndExit( + "No such file: " + topLevelArg.jsonFile); + } + } + // If args are given in a json string, (1) unpack them into an args + // array, and (2) call cmd.execute on them, which assigns them to their + // correct instance variables, then (3) recurses into run(). + if (topLevelArg.jsonString != null) { + // Unpack args from json string. + String[] args = jsonStringToArgs(topLevelArg.jsonString); + // Execute application on unpacked args. + CommandLine cmd = spec.commandLine(); + int exitCode = cmd.execute(args); + io.callSystemExit(exitCode); + // If args are already unpacked, invoke tool-specific logic. + } else { + runTool(); + } + } + + /* + * The entrypoint of tool-specific logic. + * Lfc and Lff have their own specific implementations for this method. + */ + public abstract void runTool(); public static Injector getInjector(String toolName, Io io) { final ReportingBackend reporter @@ -139,7 +203,7 @@ protected Path toAbsolutePath(Path other) { * @return Validated input paths. */ protected List getInputPaths() { - List paths = files.stream() + List paths = topLevelArg.files.stream() .map(io.getWd()::resolve) .collect(Collectors.toList()); @@ -252,4 +316,54 @@ public Resource getResource(Path path) { } } + private String[] jsonStringToArgs(String jsonString) { + ArrayList argsList = new ArrayList<>(); + JsonObject jsonObject = new JsonObject(); + + // Parse JSON string and get top-level JSON object. + try { + jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); + } catch (JsonParseException e) { + reporter.printFatalErrorAndExit( + "Invalid JSON string:\n" + jsonString); + } + // Append input paths. + JsonElement src = jsonObject.get("src"); + if (src == null) { + reporter.printFatalErrorAndExit( + "JSON Parse Exception: field \"src\" not found."); + } + argsList.add(src.getAsString()); + // Append output path if given. + JsonElement out = jsonObject.get("out"); + if (out != null) { + argsList.add("--output-path"); + argsList.add(out.getAsString()); + } + + // If there are no other properties, return args array. + JsonElement properties = jsonObject.get("properties"); + if (properties != null) { + // Get the remaining properties. + Set> entrySet = properties + .getAsJsonObject() + .entrySet(); + // Append the remaining properties to the args array. + for(Entry entry : entrySet) { + String property = entry.getKey(); + String value = entry.getValue().getAsString(); + + // Append option. + argsList.add("--" + property); + // Append argument for non-boolean options. + if (value != "true" || property == "threading") { + argsList.add(value); + } + } + } + + // Return as String[]. + String[] args = argsList.toArray(new String[argsList.size()]); + return args; + } } diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index b31e5a8246..1952edafe0 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -156,7 +156,7 @@ public static void main(Io io, final String... args) { * Load the resource, validate it, and, invoke the code generator. */ @Override - public void run() { + public void runTool() { List paths = getInputPaths(); final Path outputRoot = getOutputRoot(); // Hard code the props based on the options we want. diff --git a/org.lflang/src/org/lflang/cli/Lff.java b/org.lflang/src/org/lflang/cli/Lff.java index 1efc207128..62d29594e7 100644 --- a/org.lflang/src/org/lflang/cli/Lff.java +++ b/org.lflang/src/org/lflang/cli/Lff.java @@ -88,7 +88,7 @@ public static void main(Io io, final String... args) { * Validates all paths and invokes the formatter on the input paths. */ @Override - public void run() { + public void runTool() { List paths = getInputPaths(); final Path outputRoot = getOutputRoot(); From fb8cd3c137f794e6c0c2c23fe12f05d61986cf41 Mon Sep 17 00:00:00 2001 From: Atharva Patil Date: Tue, 4 Apr 2023 00:26:22 -0700 Subject: [PATCH 2/6] Better commenting. --- org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java index 99fb5ba3ce..7e462679de 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -210,6 +210,8 @@ public void testGenInSrcDir(@TempDir Path tempDir) throws IOException { }); } + // Helper method for comparing argument values in tests testGeneratorArgs, + // testGeneratorArgsJsonString and testGeneratorArgsJsonFile. public void verifyGeneratorArgs(Path tempDir, String[] args) { LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); From 4f0629b68620d359281632f915ed262d8c8ad58c Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 4 Apr 2023 22:24:13 -0700 Subject: [PATCH 3/6] Apply suggestions from code review --- org.lflang/src/org/lflang/cli/CliBase.java | 6 +++--- org.lflang/src/org/lflang/cli/Lfc.java | 2 +- org.lflang/src/org/lflang/cli/Lff.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index 0f235e9039..417010adba 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -169,7 +169,7 @@ public void run() { io.callSystemExit(exitCode); // If args are already unpacked, invoke tool-specific logic. } else { - runTool(); + doRun(); } } @@ -177,7 +177,7 @@ public void run() { * The entrypoint of tool-specific logic. * Lfc and Lff have their own specific implementations for this method. */ - public abstract void runTool(); + public abstract void doRun(); public static Injector getInjector(String toolName, Io io) { final ReportingBackend reporter @@ -325,7 +325,7 @@ private String[] jsonStringToArgs(String jsonString) { jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); } catch (JsonParseException e) { reporter.printFatalErrorAndExit( - "Invalid JSON string:\n" + jsonString); + String.format("Invalid JSON string:%n %s", jsonString)); } // Append input paths. JsonElement src = jsonObject.get("src"); diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index 1952edafe0..f0e88aef93 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -156,7 +156,7 @@ public static void main(Io io, final String... args) { * Load the resource, validate it, and, invoke the code generator. */ @Override - public void runTool() { + public void doRun() { List paths = getInputPaths(); final Path outputRoot = getOutputRoot(); // Hard code the props based on the options we want. diff --git a/org.lflang/src/org/lflang/cli/Lff.java b/org.lflang/src/org/lflang/cli/Lff.java index 62d29594e7..297bc5ad84 100644 --- a/org.lflang/src/org/lflang/cli/Lff.java +++ b/org.lflang/src/org/lflang/cli/Lff.java @@ -88,7 +88,7 @@ public static void main(Io io, final String... args) { * Validates all paths and invokes the formatter on the input paths. */ @Override - public void runTool() { + public void doRun() { List paths = getInputPaths(); final Path outputRoot = getOutputRoot(); From 0062e3ed6bf8259597493e1ef9784db6116a3a67 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 31 Mar 2023 21:00:25 -0700 Subject: [PATCH 4/6] Try to pass unit tests. The error gets reported with an unexpected line number (not even in the right file) when a mistake is inserted into the type for the list of time values. I do not want to fix this right now (honestly it is the C++ compiler's fault, I do not even know how we would fix this), so instead I will try to cover it up. --- .../org/lflang/tests/compiler/LinguaFrancaValidationTest.java | 4 ++-- test/Cpp/src/NativeListsAndTimes.lf | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 67df93ed30..c5ead40cb7 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1795,7 +1795,7 @@ public void testMissingModeStateResetInstance() throws Exception { String testCase = """ target C; reactor R { - state s:int(0); + state s:int = 0; } main reactor { initial mode IM { @@ -1836,7 +1836,7 @@ public void testUnspecifiedTransitionType() throws Exception { reaction(startup) -> M {==} } mode M { - reset state s:int(0); + reset state s:int = 0; } } """; diff --git a/test/Cpp/src/NativeListsAndTimes.lf b/test/Cpp/src/NativeListsAndTimes.lf index 5bf67c8fb4..f9ddfa4bd7 100644 --- a/test/Cpp/src/NativeListsAndTimes.lf +++ b/test/Cpp/src/NativeListsAndTimes.lf @@ -6,9 +6,7 @@ reactor Foo( y: time = 0, // Units are missing but not required z = 1 msec, // Type is missing but not required p: int[]{1, 2, 3, 4}, // List of integers - q: {= // list of time values - std::vector - =}{1 msec, 2 msec, 3 msec}, + q: {= std::vector =}{1 msec, 2 msec, 3 msec}, g: time[]{1 msec, 2 msec}, // List of time values g2: int[] = {} ) { From 14d3d4c32650a9a52a595e228845c8071dc33de9 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 7 Apr 2023 09:17:33 -0700 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java | 2 +- org.lflang/src/org/lflang/cli/CliBase.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java index 7e462679de..8050495e55 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -268,7 +268,7 @@ public void testGeneratorArgsJsonString(@TempDir Path tempDir) throws IOException { TempDirBuilder dir = dirBuilder(tempDir); dir.file("src/File.lf", LF_PYTHON_FILE); - dir.mkdirs("path//to/rti"); + dir.mkdirs("path/to/rti"); String[] args = {"--json", JSON_STRING}; verifyGeneratorArgs(tempDir, args); diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index 417010adba..4dea30b865 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -63,7 +63,7 @@ static class MutuallyExclusive { @Parameters( arity = "1..", paramLabel = "FILES", - description = "Paths of the files to run Lingua Franca programs on.") + description = "Paths to one or more Lingua Franca programs.") protected List files; @Option( From 02c8df904639ab36af6a2060d4fe895e63e1cf04 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 7 Apr 2023 09:17:48 -0700 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java index 8050495e55..477bd26c32 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -280,7 +280,7 @@ public void testGeneratorArgsJsonFile(@TempDir Path tempDir) TempDirBuilder dir = dirBuilder(tempDir); dir.file("src/File.lf", LF_PYTHON_FILE); dir.file("src/test.json", JSON_STRING); - dir.mkdirs("path//to/rti"); + dir.mkdirs("path/to/rti"); String[] args = {"--json-file", "src/test.json"}; verifyGeneratorArgs(tempDir, args);