diff --git a/.github/workflows/all-misc.yml b/.github/workflows/all-misc.yml index 7955c3c6ed..45349e1513 100644 --- a/.github/workflows/all-misc.yml +++ b/.github/workflows/all-misc.yml @@ -21,31 +21,3 @@ concurrency: jobs: check-diff: uses: ./.github/workflows/check-diff.yml - - # Test the Gradle build. - building: - needs: check-diff - uses: ./.github/workflows/build.yml - with: - all-platforms: ${{ !github.event.pull_request.draft }} - if: ${{ needs.check-diff.outputs.run_build == 'true' }} - - # Run tests for the standalone compiler. - cli: - if: ${{ needs.check-diff.outputs.run_misc == 'true' }} - needs: check-diff - uses: ./.github/workflows/cli-tests.yml - with: - all-platforms: ${{ !github.event.pull_request.draft }} - - # Run language server tests. - lsp: - if: ${{ needs.check-diff.outputs.run_misc == 'true' }} - needs: check-diff - uses: ./.github/workflows/lsp-tests.yml - with: - all-platforms: ${{ !github.event.pull_request.draft }} - - check-labels: - uses: ./.github/workflows/check-labels.yml - if: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/all-targets.yml b/.github/workflows/all-targets.yml index 4068bc2576..be5f0dff5c 100644 --- a/.github/workflows/all-targets.yml +++ b/.github/workflows/all-targets.yml @@ -19,35 +19,5 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - check-diff: - uses: ./.github/workflows/check-diff.yml - c: uses: ./.github/workflows/only-c.yml - needs: check-diff - if: ${{ needs.check-diff.outputs.run_c == 'true' }} - - cpp: - uses: ./.github/workflows/only-cpp.yml - needs: check-diff - if: ${{ needs.check-diff.outputs.run_cpp == 'true' }} - - py: - uses: ./.github/workflows/only-py.yml - needs: check-diff - if: ${{ needs.check-diff.outputs.run_py == 'true' || needs.check-diff.outputs.run_c == 'true' }} - - rs: - uses: ./.github/workflows/only-rs.yml - needs: check-diff - if: ${{ needs.check-diff.outputs.run_rs == 'true' }} - - ts: - uses: ./.github/workflows/only-ts.yml - needs: check-diff - if: ${{ needs.check-diff.outputs.run_ts == 'true' }} - - serialization: - if: ${{ needs.check-diff.outputs.run_c == 'true' || needs.check-diff.outputs.run_py == 'true' || needs.check-diff.outputs.run_ts == 'true' }} - needs: check-diff - uses: ./.github/workflows/serialization-tests.yml diff --git a/.github/workflows/c-tests-with-rust-rti.yml b/.github/workflows/c-tests-with-rust-rti.yml new file mode 100644 index 0000000000..8a300ea859 --- /dev/null +++ b/.github/workflows/c-tests-with-rust-rti.yml @@ -0,0 +1,48 @@ +name: C tests + +on: + workflow_call: + inputs: + compiler-ref: + required: false + type: string + runtime-ref: + required: false + type: string + use-cpp: + required: false + type: boolean + default: false + scheduler: + required: false + type: string + all-platforms: + required: false + default: true + type: boolean + +jobs: + regular-tests: + strategy: + matrix: + platform: ${{ (inputs.all-platforms && fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]')) || fromJSON('["ubuntu-latest"]') }} + runs-on: ${{ matrix.platform }} + timeout-minutes: 120 + steps: + - name: Check out lingua-franca repository + uses: actions/checkout@v3 + with: + repository: chanijjani/lingua-franca + submodules: true + ref: ${{ inputs.compiler-ref }} + fetch-depth: 0 + - name: Prepare build environment + uses: ./.github/actions/prepare-build-env + - name: Perform tests for C target with default scheduler + run: ./gradlew targetTest -Ptarget=C + if: ${{ !inputs.use-cpp && !inputs.scheduler }} + - name: Perform tests for C target with specified scheduler (no LSP tests) + run: | + echo "Specified scheduler: ${{ inputs.scheduler }}" + ./gradlew targetTest -Ptarget=C -Dscheduler=${{ inputs.scheduler }} + if: ${{ !inputs.use-cpp && inputs.scheduler }} diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index ae9016465c..9a27268332 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -13,35 +13,8 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} jobs: - # Run the C integration tests. + # Run the C integration tests with Rust RTI. default: - uses: ./.github/workflows/c-tests.yml + uses: ./.github/workflows/c-tests-with-rust-rti.yml with: all-platforms: ${{ !github.event.pull_request.draft }} - - # Run the C benchmark tests. - benchmarking: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: "C" - - # Run the C Arduino integration tests. - arduino: - uses: ./.github/workflows/c-arduino-tests.yml - with: - all-platforms: ${{ !github.event.pull_request.draft }} - - # Run the C Zephyr integration tests. - zephyr: - uses: ./.github/workflows/c-zephyr-tests.yml - - # Run the CCpp integration tests. - ccpp: - uses: ./.github/workflows/c-tests.yml - with: - use-cpp: true - all-platforms: ${{ !github.event.pull_request.draft }} - - # Run the Uclid-based LF Verifier benchmarks. - verifier: - uses: ./.github/workflows/c-verifier-tests.yml diff --git a/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java b/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java index 12c71eed7c..0c588719c8 100644 --- a/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java @@ -1,5 +1,6 @@ package org.lflang.tests; +import java.nio.file.Path; import java.util.EnumSet; import java.util.List; import org.junit.jupiter.api.Assumptions; @@ -142,6 +143,21 @@ public void runFederatedTests() { false); } + @Test + // public void runFederatedTestsWithRustRti(Path rustRtiProjectPath) { + public void runFederatedTestsWithRustRti() { + Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); + runTestsForTargetsWithRustRti( + Message.DESC_FEDERATED, + TestCategory.FEDERATED::equals, + Transformers::noChanges, + Configurators::noChanges, + TestLevel.EXECUTION, + // false, + // rustRtiProjectPath); + false); + } + /** Run the tests for modal reactors. */ @Test public void runModalTests() { diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/RustRtiCTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/RustRtiCTest.java new file mode 100644 index 0000000000..e9c7b94846 --- /dev/null +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/RustRtiCTest.java @@ -0,0 +1,104 @@ +/************* + * Copyright (c) 2019, The University of California at Berkeley. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ +package org.lflang.tests.runtime; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.lflang.target.Target; +import org.lflang.tests.RuntimeTest; + +/** + * Collection of tests for the C target. + * + *

Tests that are implemented in the base class are still overridden so that each test can be + * easily invoked individually from IDEs with JUnit support like Eclipse and IntelliJ. This is + * typically done by right-clicking on the name of the test method and then clicking "Run".* + * + * @author Marten Lohstroh + * @author Chanhee Lee + */ +public class RustRtiCTest extends RuntimeTest { + + public RustRtiCTest() { + super(Target.C); + } + + @Override + protected boolean supportsSingleThreadedExecution() { + return true; + } + + @Override + protected boolean supportsFederatedExecution() { + return true; + } + + @Override + protected boolean supportsDockerOption() { + return true; + } + + @Test + @Override + public void runBasicTests() { + super.runBasicTests(); + } + + @Test + @Override + public void runGenericsTests() { + super.runGenericsTests(); + } + + @Test + @Override + public void runTargetSpecificTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runTargetSpecificTests(); + } + + @Test + @Override + public void runMultiportTests() { + super.runMultiportTests(); + } + + @Test + @Override + public void runWithThreadingOff() { + super.runWithThreadingOff(); + } + + @Test + // public void runFederatedTests(Path rustRtiProjectPath) { + public void runFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + // super.runFederatedTestsWithRustRti(rustRtiProjectPath); + super.runFederatedTestsWithRustRti(); + } +} diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 0acf495fc7..ed1d23d908 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -31,6 +31,7 @@ import org.lflang.ast.ASTUtils; import org.lflang.federated.launcher.FedLauncherGenerator; import org.lflang.federated.launcher.RtiConfig; +import org.lflang.federated.launcher.RustRtiConfig; import org.lflang.generator.CodeMap; import org.lflang.generator.GeneratorArguments; import org.lflang.generator.GeneratorResult.Status; @@ -201,6 +202,98 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws return false; } + /** + * Produce LF code for each federate in a separate file, then invoke a target-specific code + * generator for each of those files. + * + * @param resource The resource that has the federated main reactor in it + * @param context The context in which to carry out the code generation. + * @return False if no errors have occurred, true otherwise. + */ + // public boolean doGenerateForRustRTI(Resource resource, LFGeneratorContext context, Path rustRtiProjectPath) throws IOException { + public boolean doGenerateForRustRTI(Resource resource, LFGeneratorContext context) throws IOException { + if (!federatedExecutionIsSupported(resource)) return true; + cleanIfNeeded(context); + + // In a federated execution, we need keepalive to be true, + // otherwise a federate could exit simply because it hasn't received + // any messages. + KeepaliveProperty.INSTANCE.override(targetConfig, true); + + // Process command-line arguments + processCLIArguments(context); + + // Find the federated reactor + Reactor federation = FedASTUtils.findFederatedReactor(resource); + + // Make sure the RTI host is set correctly. + setRTIHost(federation); + + // Create the FederateInstance objects. + ReactorInstance main = createFederateInstances(federation, context); + + // Insert reactors that split multiports into many ports. + insertIndexers(main, resource); + + // Clear banks so that each bank member becomes a single federate. + for (Instantiation instantiation : ASTUtils.allInstantiations(federation)) { + instantiation.setWidthSpec(null); + instantiation.setWidthSpec(null); + } + + // Find all the connections between federates. + // For each connection between federates, replace it in the + // AST with an action (which inherits the delay) and three reactions. + // The action will be physical for physical connections and logical + // for logical connections. + replaceFederateConnectionsWithProxies(federation, main, resource); + + FedEmitter fedEmitter = + new FedEmitter( + fileConfig, + ASTUtils.toDefinition(mainDef.getReactorClass()), + messageReporter, + rtiConfig); + + // Generate LF code for each federate. + Map lf2lfCodeMapMap = new HashMap<>(); + for (FederateInstance federate : federates) { + lf2lfCodeMapMap.putAll(fedEmitter.generateFederate(context, federate, federates.size())); + } + + // Do not invoke target code generators if --no-compile flag is used. + if (context.getTargetConfig().get(NoCompileProperty.INSTANCE)) { + context.finish(Status.GENERATED, lf2lfCodeMapMap); + return false; + } + + // If the RTI is to be built locally, set up a build environment for it. + prepareRtiBuildEnvironment(context); + + Map codeMapMap = + compileFederates( + context, + lf2lfCodeMapMap, + subContexts -> { + createDockerFiles(context, subContexts); + // generateLaunchScriptForRustRti(rustRtiProjectPath); + generateLaunchScriptForRustRti(); + // If an error has occurred during codegen of any federate, report it. + subContexts.forEach( + c -> { + if (c.getErrorReporter().getErrorsOccurred()) { + context + .getErrorReporter() + .at(c.getFileConfig().srcFile) + .error("Failure during code generation of " + c.getFileConfig().srcFile); + } + }); + }); + + context.finish(Status.COMPILED, codeMapMap); + return false; + } + /** * Prepare a build environment for the rti alongside the generated sources of the federates. * @@ -229,6 +322,13 @@ private void generateLaunchScript() { .doGenerate(federates, rtiConfig); } + // private void generateLaunchScriptForRustRti(Path rustRtiProjectPath) { + private void generateLaunchScriptForRustRti() { + new FedLauncherGenerator(this.targetConfig, this.fileConfig, this.messageReporter) + // .doGenerateForRustRTI(federates, new RustRtiConfig(rustRtiProjectPath)); + .doGenerateForRustRTI(federates, new RustRtiConfig()); + } + /** * Generate a Dockerfile for each federate and a docker-compose.yml for the federation. * diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index 5ec59ecb34..4605635967 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -265,6 +265,172 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { } } + /** + * Create the launcher shell scripts. This will create one or two files in the output path (bin + * directory). The first has name equal to the filename of the source file without the ".lf" + * extension. This will be a shell script that launches the RTI and the federates. If, in + * addition, either the RTI or any federate is mapped to a particular machine (anything other than + * the default "localhost" or "0.0.0.0"), then this will generate a shell script in the bin + * directory with name filename_distribute.sh that copies the relevant source files to the remote + * host and compiles them so that they are ready to execute using the launcher. + * + *

A precondition for this to work is that the user invoking this code generator can log into + * the remote host without supplying a password. Specifically, you have to have installed your + * public key (typically found in ~/.ssh/id_rsa.pub) in ~/.ssh/authorized_keys on the remote host. + * In addition, the remote host must be running an ssh service. On an Arch Linux system using + * systemd, for example, this means running: + * + *

sudo systemctl ssh.service + * + *

Enable means to always start the service at startup, whereas start means to just start it + * this once. + * + * @param federates A list of federate instances in the federation + * @param rustRtiConfig Can have values for 'host', 'dir', 'user', and 'projectPath' + */ + public void doGenerateForRustRTI(List federates, RustRtiConfig rustRtiConfig) { + // NOTE: It might be good to use screen when invoking the RTI + // or federates remotely, so you can detach and the process keeps running. + // However, I was unable to get it working properly. + // What this means is that the shell that invokes the launcher + // needs to remain live for the duration of the federation. + // If that shell is killed, the federation will die. + // Hence, it is reasonable to launch the federation on a + // machine that participates in the federation, for example, + // on the machine that runs the RTI. The command I tried + // to get screen to work looks like this: + // ssh -t «target» cd «path»; screen -S «filename»_«federate.name» -L + // bin/«filename»_«federate.name» 2>&1 + // var outPath = binGenPath + StringBuilder shCode = new StringBuilder(); + StringBuilder distCode = new StringBuilder(); + shCode.append(getSetupCode()).append("\n"); + String distHeader = getDistHeader(); + String host = rustRtiConfig.getHost(); + String target = host; + + String user = rustRtiConfig.getUser(); + if (user != null) { + target = user + "@" + host; + } + + shCode.append("#### Host is ").append(host); + + // Launch the RTI in the foreground. + if (host.equals("localhost") || host.equals("0.0.0.0")) { + // FIXME: the paths below will not work on Windows + shCode.append(getLaunchCodeForRustRti(getRtiCommand(federates, false), rustRtiConfig.getProjectPath())).append("\n"); + } else { + // Start the RTI on the remote machine - Not supported yet for Rust RTI. + } + + // Index used for storing pids of federates + int federateIndex = 0; + for (FederateInstance federate : federates) { + var buildConfig = getBuildConfig(federate, fileConfig, messageReporter); + if (federate.isRemote) { + Path fedRelSrcGenPath = + fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath()).resolve(federate.name); + if (distCode.isEmpty()) distCode.append(distHeader).append("\n"); + String logFileName = String.format("log/%s_%s.log", fileConfig.name, federate.name); + distCode.append(getDistCode(rustRtiConfig.getDirectory(), federate)).append("\n"); + shCode + .append(getFedRemoteLaunchCode(rustRtiConfig.getDirectory(), federate, federateIndex++)) + .append("\n"); + } else { + String executeCommand = buildConfig.localExecuteCommand(); + shCode + .append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++)) + .append("\n"); + } + } + if (host.equals("localhost") || host.equals("0.0.0.0")) { + // Local PID managements + shCode.append( + "echo \"#### Bringing the RTI back to foreground so it can receive Control-C.\"" + "\n"); + shCode.append("fg %1" + "\n"); + } + // Wait for launched processes to finish + shCode + .append( + String.join( + "\n", + "echo \"RTI has exited. Wait for federates to exit.\"", + "# Wait for launched processes to finish.", + "# The errors are handled separately via trap.", + "for pid in \"${pids[@]}\"", + "do", + " wait $pid || exit $?", + "done", + "echo \"All done.\"", + "EXITED_SUCCESSFULLY=true")) + .append("\n"); + + // Create bin directory for the script. + if (!Files.exists(fileConfig.binPath)) { + try { + Files.createDirectories(fileConfig.binPath); + } catch (IOException e) { + messageReporter.nowhere().error("Unable to create directory: " + fileConfig.binPath); + } + } + + // Write the launcher file. + File file = fileConfig.binPath.resolve(fileConfig.name).toFile(); + messageReporter.nowhere().info("Script for launching the federation: " + file); + + // Delete file previously produced, if any. + if (file.exists()) { + if (!file.delete()) + messageReporter + .nowhere() + .error("Failed to delete existing federated launch script \"" + file + "\""); + } + + FileOutputStream fOut = null; + try { + fOut = new FileOutputStream(file); + } catch (FileNotFoundException e) { + messageReporter.nowhere().error("Unable to find file: " + file); + } + if (fOut != null) { + try { + fOut.write(shCode.toString().getBytes()); + fOut.close(); + } catch (IOException e) { + messageReporter.nowhere().error("Unable to write to file: " + file); + } + } + + if (!file.setExecutable(true, false)) { + messageReporter.nowhere().warning("Unable to make launcher script executable."); + } + + // Write the distributor file. + // Delete the file even if it does not get generated. + file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile(); + if (file.exists()) { + if (!file.delete()) + messageReporter + .nowhere() + .error("Failed to delete existing federated distributor script \"" + file + "\""); + } + if (distCode.length() > 0) { + try { + fOut = new FileOutputStream(file); + fOut.write(distCode.toString().getBytes()); + fOut.close(); + if (!file.setExecutable(true, false)) { + messageReporter.nowhere().warning("Unable to make file executable: " + file); + } + } catch (FileNotFoundException e) { + messageReporter.nowhere().error("Unable to find file: " + file); + } catch (IOException e) { + messageReporter.nowhere().error("Unable to write to file " + file); + } + } + } + private String getSetupCode() { return String.join( "\n", @@ -377,6 +543,36 @@ private String getLaunchCode(String rtiLaunchCode) { "sleep 1"); } + private String getLaunchCodeForRustRti(String rtiLaunchCode, Path rustRtiProjectPath) { + String launchCodeWithLogging = String.join(" ", rtiLaunchCode, ">& RTI.log &"); + String launchCodeWithoutLogging = String.join(" ", rtiLaunchCode, "&"); + return String.join( + "\n", + "echo \"#### Launching the Rust runtime infrastructure (RTI).\"", + "# The Rust RTI is started first to allow proper boot-up", + "# before federates will try to connect.", + "# The RTI will be brought back to foreground", + "# to be responsive to user inputs after all federates", + "# are launched.", + "LF_RUST_RTI_PATH=\"find ~/ -name lf-rust-rti\"", + "cd %{LF_RUST_RTI_PATH}", + "cargo run -- -i ${FEDERATION_ID} \\", + " -n 2 \\", + " -c init \\", + "&", + "if [ \"$1\" = \"-l\" ]; then", + launchCodeWithLogging, + "else", + launchCodeWithoutLogging, + "fi", + "# Store the PID of the RTI", + "RTI=$!", + "# Wait for the RTI to boot up before", + "# starting federates (this could be done by waiting for a specific output", + "# from the RTI, but here we use sleep)", + "sleep 1"); + } + private String getRemoteLaunchCode( Object host, Object target, String logFileName, String rtiLaunchString) { return String.join( diff --git a/core/src/main/java/org/lflang/federated/launcher/RustRtiConfig.java b/core/src/main/java/org/lflang/federated/launcher/RustRtiConfig.java new file mode 100644 index 0000000000..c5a7f38f12 --- /dev/null +++ b/core/src/main/java/org/lflang/federated/launcher/RustRtiConfig.java @@ -0,0 +1,29 @@ +package org.lflang.federated.launcher; + +import java.nio.file.Path; + +/** + * Class for storing configuration settings pertaining to the Rust RTI. + * + * @author Chanhee Lee + */ +public class RustRtiConfig extends RtiConfig { + + private Path projectPath; + + /** Construct a new RTI configuration with all options set to their defaults. */ + public RustRtiConfig() { + super(); + } + + /** Construct a new RTI configuration with all options set to their defaults. */ + public RustRtiConfig(Path projectPath) { + super(); + this.projectPath = projectPath; + } + + /** Return the Rust RTI project path. */ + public Path getProjectPath() { + return projectPath; + } +} diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index f4e61e93d5..72d17e647f 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -121,6 +121,43 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont } } + // public void doGenerateForRustRTI(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context, Path rustRtiProjectPath) { + public void doGenerateForRustRTI(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { + assert injector != null; + final LFGeneratorContext lfContext; + if (context instanceof LFGeneratorContext) { + lfContext = (LFGeneratorContext) context; + } else { + lfContext = LFGeneratorContext.lfGeneratorContextOf(resource, fsa, context); + } + + // The fastest way to generate code is to not generate any code. + if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST) return; + + if (FedASTUtils.findFederatedReactor(resource) != null) { + try { + FedGenerator fedGenerator = new FedGenerator(lfContext); + injector.injectMembers(fedGenerator); + // generatorErrorsOccurred = fedGenerator.doGenerateForRustRTI(resource, lfContext, rustRtiProjectPath); + generatorErrorsOccurred = fedGenerator.doGenerateForRustRTI(resource, lfContext); + } catch (IOException e) { + throw new RuntimeIOException("Error during federated code generation", e); + } + + } else { + final GeneratorBase generator = createGenerator(lfContext); + + if (generator != null) { + generator.doGenerate(resource, lfContext); + generatorErrorsOccurred = generator.errorsOccurred(); + } + } + final MessageReporter messageReporter = lfContext.getErrorReporter(); + if (messageReporter instanceof LanguageServerMessageReporter) { + ((LanguageServerMessageReporter) messageReporter).publishDiagnostics(); + } + } + /** Return true if errors occurred in the last call to doGenerate(). */ public boolean errorsOccurred() { return generatorErrorsOccurred; diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index d96cbceeac..26eb3465c5 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d96cbceeac172a3c4ff9caa4520bd0b3034c713e +Subproject commit 26eb3465c5ca425a03ded2e042a7b6b35cf0181f diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index 6923149f07..61be6685b5 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -196,6 +196,39 @@ protected final void runTestsAndPrintResults( } } + /** + * Run selected tests for a given target and configurator up to the specified level. + * + * @param target The target to run tests for. + * @param selected A predicate that given a test category returns whether it should be included in + * this test run or not. + * @param configurator A procedure for configuring the tests. + * @param copy Whether to work on copies of tests in the test. registry. + */ + protected final void runTestsAndPrintResultsWithRustRti( + Target target, + Predicate selected, + TestLevel level, + Transformer transformer, + Configurator configurator, + // boolean copy, + // Path rustRtiProjectPath) { + boolean copy) { + var categories = Arrays.stream(TestCategory.values()).filter(selected).toList(); + for (var category : categories) { + System.out.println(category.getHeader()); + var tests = testRegistry.getRegisteredTests(target, category, copy); + try { + // validateAndRunWithRustRti(tests, transformer, configurator, level, rustRtiProjectPath); + validateAndRunWithRustRti(tests, transformer, configurator, level); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + System.out.println(testRegistry.getCoverageReport(target, category)); + checkAndReportFailures(tests); + } + } + /** * Run tests in the given selection for all targets enabled in this class. * @@ -217,6 +250,30 @@ protected void runTestsForTargets( } } + /** + * Run tests in the given selection for all targets enabled in this class. + * + * @param description A string that describes the collection of tests. + * @param selected A predicate that given a test category returns whether it should be included in + * this test run or not. + * @param configurator A procedure for configuring the tests. + * @param copy Whether to work on copies of tests in the test. registry. + */ + protected void runTestsForTargetsWithRustRti( + String description, + Predicate selected, + Transformer transformer, + Configurator configurator, + TestLevel level, + // boolean copy, + // Path rustRtiProjectPath) { + boolean copy) { + for (Target target : this.targets) { + // runTestsForRustRti(List.of(target), description, selected, transformer, configurator, level, copy, rustRtiProjectPath); + runTestsForRustRti(List.of(target), description, selected, transformer, configurator, level, copy); + } + } + /** * Run tests in the given selection for a subset of given targets. * @@ -241,6 +298,33 @@ protected void runTestsFor( } } + /** + * Run tests in the given selection for a subset of given targets. + * + * @param subset The subset of targets to run the selected tests for. + * @param description A string that describes the collection of tests. + * @param selected A predicate that given a test category returns whether it should be included in + * this test run or not. + * @param configurator A procedure for configuring the tests. + * @param copy Whether to work on copies of tests in the test. registry. + */ + protected void runTestsForRustRti( + List subset, + String description, + Predicate selected, + Transformer transformer, + Configurator configurator, + TestLevel level, + // boolean copy, + // Path rustRtiProjectPath) { + boolean copy) { + for (Target target : subset) { + printTestHeader(target, description); + // runTestsAndPrintResultsWithRustRti(target, selected, level, transformer, configurator, copy, rustRtiProjectPath); + runTestsAndPrintResultsWithRustRti(target, selected, level, transformer, configurator, copy); + } + } + /** Whether to enable threading. */ protected boolean supportsSingleThreadedExecution() { return false; @@ -496,6 +580,27 @@ private void generateCode(LFTest test) throws TestError { } } + /** + * Invoke the code generator for the given test. + * + * @param test The test to generate code for. + */ + // private void generateCodeForRustRti(LFTest test, Path rustRtiProjectPath) throws TestError { + private void generateCodeForRustRti(LFTest test) throws TestError { + if (test.getFileConfig().resource == null) { + test.getContext().finish(GeneratorResult.NOTHING); + } + try { + // generator.doGenerateForRustRTI(test.getFileConfig().resource, fileAccess, test.getContext(), rustRtiProjectPath); + generator.doGenerateForRustRTI(test.getFileConfig().resource, fileAccess, test.getContext()); + } catch (Throwable e) { + throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL, e); + } + if (generator.errorsOccurred()) { + throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL); + } + } + /** * Given an indexed test, execute it and label the test as failing if it did not execute, took too * long to execute, or executed but exited with an error code. @@ -712,4 +817,51 @@ private void validateAndRun( System.out.print(System.lineSeparator()); } + + /** + * Validate and run the given tests, using the specified configuratator and level. + * + *

While performing tests, this method prints a header that reaches completion once all tests + * have been run. + * + * @param tests A set of tests to run. + * @param transformer A procedure for transforming the tests. + * @param configurator A procedure for configuring the tests. + * @param level The level of testing. + * @throws IOException If initial file configuration fails + */ + private void validateAndRunWithRustRti( + // Set tests, Transformer transformer, Configurator configurator, TestLevel level, Path rustRtiProjectPath) + Set tests, Transformer transformer, Configurator configurator, TestLevel level) + throws IOException { + var done = 1; + + System.out.println(THICK_LINE); + + for (var test : tests) { + System.out.println( + "Running: " + test.toString() + " (" + (int) (done / (float) tests.size() * 100) + "%)"); + try { + test.redirectOutputs(); + prepare(test, transformer, configurator); + validate(test); + // generateCodeForRustRti(test, rustRtiProjectPath); + generateCodeForRustRti(test); + if (level == TestLevel.EXECUTION) { + execute(test); + } + test.markPassed(); + } catch (TestError e) { + test.handleTestError(e); + } catch (Throwable e) { + test.handleTestError( + new TestError("Unknown exception during test execution", Result.TEST_EXCEPTION, e)); + } finally { + test.restoreOutputs(); + } + done++; + } + + System.out.print(System.lineSeparator()); + } }