diff --git a/.github/scripts/test-lfc.sh b/.github/scripts/test-lfc.sh index a59d4acd3b..096736f495 100755 --- a/.github/scripts/test-lfc.sh +++ b/.github/scripts/test-lfc.sh @@ -16,54 +16,5 @@ function test_with_links() { bin/lfc test/C/src/Minimal.lf -# -c,--clean Clean before building. -bin/lfc -c test/C/src/Minimal.lf -bin/lfc --clean test/C/src/Minimal.lf - -# --external-runtime-path Specify an external runtime library to -# be used by the compiled binary. - -# -f,--federated Treat main reactor as federated. -bin/lfc -f test/C/src/Minimal.lf -bin/lfc --federated test/C/src/Minimal.lf - -# --rti Specify address of RTI. -bin/lfc -f --rti rti test/C/src/Minimal.lf -bin/lfc --federated --rti rti test/C/src/Minimal.lf - -# -h,--help Display this information. -bin/lfc -h -bin/lfc --help - -# -l, --lint Enable linting during build. -bin/lfc -l test/Python/src/Minimal.lf -bin/lfc --lint test/Python/src/Minimal.lf - -# -n,--no-compile Do not invoke target compiler. -bin/lfc -n test/C/src/Minimal.lf -bin/lfc --no-compile test/C/src/Minimal.lf - -# -o,--output-path Specify the root output directory. -bin/lfc -o . test/C/src/Minimal.lf -bin/lfc --output-path . test/C/src/Minimal.lf - -# --runtime-version Specify the version of the runtime -# library used for compiling LF -# programs. -bin/lfc --runtime-version e80cd36ce5bd625a7b167e7dfd65d25f78b0dd01 test/Cpp/src/Minimal.lf - -# -w,--workers Specify the default number of worker threads. -bin/lfc -w 2 test/C/src/Minimal.lf -bin/lfc --workers 2 test/C/src/Minimal.lf -bin/lfc --threading true test/C/src/Minimal.lf -bin/lfc --threading false test/C/src/Minimal.lf - -# --target-compiler Target compiler to invoke. -# (Added no-compile to avoid adding dependency.) -bin/lfc --target-compiler gcc --no-compile test/C/src/Minimal.lf - -# --version -bin/lfc --version - # Ensure that lfc is robust to symbolic links. test_with_links "lfc" 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 4cec8b2dd3..f3abd9d869 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -26,13 +26,17 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.lflang.tests.TestUtils.TempDirBuilder.dirBuilder; import static org.lflang.tests.TestUtils.TempDirChecker.dirChecker; import static org.lflang.tests.TestUtils.isDirectory; import static org.lflang.tests.TestUtils.isRegularFile; +import com.google.inject.Injector; + import java.io.IOException; import java.nio.file.Path; +import java.util.Properties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -40,9 +44,12 @@ import org.lflang.LocalStrings; import org.lflang.cli.Io; import org.lflang.cli.Lfc; +import org.lflang.generator.LFGeneratorContext.BuildParm; +import org.lflang.tests.TestUtils.TempDirBuilder; /** * @author Clément Fournier + * @author Atharva Patil */ public class LfcCliTest { @@ -55,7 +62,6 @@ public class LfcCliTest { } """; - @Test public void testHelpArg() { lfcTester.run("--help", "--version") @@ -86,6 +92,63 @@ public void testWrongCliArg() { }); } + @Test + public void testInvalidArgs(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/File.lf", LF_PYTHON_FILE); + LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); + + // Invalid src file. + fixture.run(tempDir, "unknown.lf") + .verify(result -> { + result.checkStdErr(containsString("No such file or directory.")); + result.checkFailed(); + }); + + // Invalid output path. + fixture.run(tempDir, "--output-path", "unknown/output/path", "src/File.lf") + .verify(result -> { + result.checkStdErr(containsString("Output location does not exist.")); + result.checkFailed(); + }); + + // Invalid build type. + fixture.run(tempDir, "--build-type", "unknown-build-type", "src/File.lf") + .verify(result -> { + result.checkStdErr(containsString("Invalid build type.")); + result.checkFailed(); + }); + + // Invalid logging level. + fixture.run(tempDir, "--logging", "unknown_level", "src/File.lf") + .verify(result -> { + result.checkStdErr(containsString("Invalid log level.")); + result.checkFailed(); + }); + + // Invalid RTI path. + fixture.run(tempDir, "--rti", "unknown/rti/path", "src/File.lf") + .verify(result -> { + result.checkStdErr(containsString("Invalid RTI path.")); + result.checkFailed(); + }); + + // Invalid scheduler. + fixture.run(tempDir, "--scheduler", "unknown-scheduler", "src/File.lf") + .verify(result -> { + result.checkStdErr(containsString("Invalid scheduler.")); + result.checkFailed(); + }); + + // Invalid workers. + fixture.run(tempDir, "--workers", "notaninteger", "src/File.lf") + .verify(result -> { + result.checkStdErr(containsString("Invalid value for option '--workers'")); + result.checkStdErr(containsString("is not an int")); + result.checkFailed(); + }); + + } + @Test public void testGenInSrcDir(@TempDir Path tempDir) throws IOException { dirBuilder(tempDir).file("src/File.lf", LF_PYTHON_FILE); @@ -98,17 +161,74 @@ public void testGenInSrcDir(@TempDir Path tempDir) throws IOException { .check("bin", isDirectory()) .check("src-gen/File/File.py", isRegularFile()); }); - } + @Test + public void testGeneratorArgs(@TempDir Path tempDir) + throws IOException { + TempDirBuilder dir = dirBuilder(tempDir); + dir.file("src/File.lf", LF_PYTHON_FILE); + dir.mkdirs("path//to/rti"); + + String[] args = { + "src/File.lf", + "--output-path", "src", + "--build-type", "Release", + "--clean", + "--target-compiler", "gcc", + "--external-runtime-path", "src", + "--federated", + "--logging", "info", + "--lint", + "--no-compile", + "--quiet", + "--rti", "path/to/rti", + "--runtime-version", "rs", + "--scheduler", "GEDF_NP", + "--threading", "false", + "--workers", "1", + }; + 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"); + }); + } static class LfcTestFixture extends CliToolTestFixture { - @Override protected void runCliProgram(Io io, String[] args) { Lfc.main(io, args); } } + static class LfcOneShotTestFixture extends CliToolTestFixture { + + private Lfc lfc; + + @Override + protected void runCliProgram(Io io, String[] args) { + // Injector used to obtain Main instance. + final Injector injector = Lfc.getInjector("lfc", io); + // Main instance. + this.lfc = injector.getInstance(Lfc.class); + lfc.doExecute(io, args); + } + } } diff --git a/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java b/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java index 2bbbada212..3a34e9cb81 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java @@ -130,7 +130,7 @@ public void testNoSuchFile(@TempDir Path tempDir) { result.checkFailed(); result.checkStdErr(containsString( - tempDir.resolve("nosuchdir") + ": No such file or directory")); + tempDir.resolve("nosuchdir") + ": No such file or directory.")); } diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 3f38360d67..1bacb1f75e 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -383,10 +383,6 @@ public enum TargetProperty { (config, value, err) -> { config.logLevel = (LogLevel) UnionType.LOGGING_UNION .forName(ASTUtils.elementToSingleString(value)); - }, - (config, value, err) -> { - config.logLevel = (LogLevel) UnionType.LOGGING_UNION - .forName(ASTUtils.elementToSingleString(value)); }), /** diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index d4dfe9c782..b825d91966 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -97,7 +97,11 @@ protected static void cliMain( // Main instance. final CliBase main = injector.getInstance(toolClass); // Parse arguments and execute main logic. - CommandLine cmd = new CommandLine(main) + main.doExecute(io, args); + } + + public void doExecute(Io io, String[] args) { + CommandLine cmd = new CommandLine(this) .setOut(new PrintWriter(io.getOut())) .setErr(new PrintWriter(io.getErr())); int exitCode = cmd.execute(args); @@ -111,7 +115,7 @@ protected static void cliMain( */ public abstract void run(); - protected static Injector getInjector(String toolName, Io io) { + public static Injector getInjector(String toolName, Io io) { final ReportingBackend reporter = new ReportingBackend(io, toolName + ": "); @@ -142,7 +146,7 @@ protected List getInputPaths() { for (Path path : paths) { if (!Files.exists(path)) { reporter.printFatalErrorAndExit( - path + ": No such file or directory"); + path + ": No such file or directory."); } } @@ -160,11 +164,11 @@ protected Path getOutputRoot() { root = io.getWd().resolve(outputPath).normalize(); if (!Files.exists(root)) { // FIXME: Create it instead? reporter.printFatalErrorAndExit( - "Output location '" + root + "' does not exist."); + root + ": Output location does not exist."); } if (!Files.isDirectory(root)) { reporter.printFatalErrorAndExit( - "Output location '" + root + "' is not a directory."); + root + ": Output location is not a directory."); } } @@ -247,4 +251,5 @@ public Resource getResource(Path path) { return null; } } + } diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index 489e9cd2f4..b31e5a8246 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -1,6 +1,7 @@ package org.lflang.cli; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Properties; @@ -14,6 +15,7 @@ import org.lflang.ASTUtils; import org.lflang.FileConfig; +import org.lflang.TargetProperty.UnionType; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.LFGeneratorContext.BuildParm; @@ -105,7 +107,7 @@ public class Lfc extends CliBase { @Option( names = {"-r", "--rti"}, description = "Specify the location of the RTI.") - private String rti; + private Path rti; @Option( names = "--runtime-version", @@ -158,7 +160,7 @@ public void run() { List paths = getInputPaths(); final Path outputRoot = getOutputRoot(); // Hard code the props based on the options we want. - Properties properties = this.filterPassOnProps(); + Properties properties = this.getGeneratorArgs(); try { // Invoke the generator on all input file paths. @@ -229,45 +231,78 @@ private Path getActualOutputPath(Path root, Path path) { * * @return Properties for the code generator. */ - protected Properties filterPassOnProps() { + public Properties getGeneratorArgs() { Properties props = new Properties(); if (buildType != null) { + // Validate build type. + if (UnionType.BUILD_TYPE_UNION.forName(buildType) == null) { + reporter.printFatalErrorAndExit( + buildType + ": Invalid build type."); + } props.setProperty(BuildParm.BUILD_TYPE.getKey(), buildType); } + if (clean) { props.setProperty(BuildParm.CLEAN.getKey(), "true"); } + if (externalRuntimePath != null) { - props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), externalRuntimePath.toString()); + props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), + externalRuntimePath.toString()); } + if (lint) { props.setProperty(BuildParm.LINT.getKey(), "true"); } + if (logging != null) { + // Validate log level. + if (UnionType.LOGGING_UNION.forName(logging) == null) { + reporter.printFatalErrorAndExit( + logging + ": Invalid log level."); + } props.setProperty(BuildParm.LOGGING.getKey(), logging); } + if (noCompile) { props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); } + if (targetCompiler != null) { props.setProperty(BuildParm.TARGET_COMPILER.getKey(), targetCompiler); } + if (quiet) { props.setProperty(BuildParm.QUIET.getKey(), "true"); } + if (rti != null) { - props.setProperty(BuildParm.RTI.getKey(), rti); + // Validate RTI path. + if (!Files.exists(io.getWd().resolve(rti))) { + reporter.printFatalErrorAndExit( + rti + ": Invalid RTI path."); + } + props.setProperty(BuildParm.RTI.getKey(), rti.toString()); } + if (runtimeVersion != null) { props.setProperty(BuildParm.RUNTIME_VERSION.getKey(), runtimeVersion); } + if (scheduler != null) { + // Validate scheduler. + if (UnionType.SCHEDULER_UNION.forName(scheduler) == null) { + reporter.printFatalErrorAndExit( + scheduler + ": Invalid scheduler."); + } props.setProperty(BuildParm.SCHEDULER.getKey(), scheduler); } + if (threading != null) { props.setProperty(BuildParm.THREADING.getKey(), threading); } + if (workers != null) { props.setProperty(BuildParm.WORKERS.getKey(), workers.toString()); }