diff --git a/test/Cpp/src/ActionIsPresent.lf b/test/Cpp/src/ActionIsPresent.lf new file mode 100644 index 0000000000..17409bd8a3 --- /dev/null +++ b/test/Cpp/src/ActionIsPresent.lf @@ -0,0 +1,27 @@ +// Tests the is_present variable for actions in cpp +target Cpp; + +main reactor ActionIsPresent(offset:time(1 nsec), period:time(500 msec)) { + logical action a; + state success:bool(false); + state zero:time(0 nsec); + reaction(startup, a) -> a {= + if (!a.is_present()) { + if (offset == zero) { + std::cout << "Hello World!" << '\n'; + success = true; + } else { + a.schedule(offset); + } + } else { + std::cout << "Hello World 2!" << '\n'; + success = true; + } + =} + reaction(shutdown) {= + if (!success) { + std::cerr << "Failed to print 'Hello World!'" << '\n'; + exit(1); + } + =} +} \ No newline at end of file diff --git a/test/Cpp/src/Alignment.lf b/test/Cpp/src/Alignment.lf new file mode 100644 index 0000000000..df75c56331 --- /dev/null +++ b/test/Cpp/src/Alignment.lf @@ -0,0 +1,100 @@ +// This test checks that the downstream reaction is not invoked more +// than once at a logical time. +target Cpp { + logging: LOG, + timeout: 1 sec, + build-type: Debug +} + +reactor Source { + output out:int; + state count:int(1); + timer t(0, 100 msec); + reaction(t) -> out {= + count++; + out.set(count); + =} +} + +reactor Sieve { + private preamble {= + #include "reactor-cpp/logging.hh" + =} + input in:int; + output out:bool; + state primes:std::vector; + reaction(startup) {= + // There are 1229 primes between 1 and 10,000. + // Primes 1 and 2 are not on the list. + primes.push_back(3); + =} + reaction(in) -> out {= + // Reject out of bounds inputs + if(*in.get() <= 0 || *in.get() > 10000) { + reactor::log::Warn() << "Sieve: Input value out of range: " << *in.get(); + } + // Primes 1 and 2 are not on the list. + if (*in.get() == 1 || *in.get() == 2) { + out.set(true); + return; + } + // If the input is greater than the last found prime, then + // we have to expand the list of primes before checking to + // see whether this is prime. + int candidate = primes.back(); + reactor::log::Info() << "Sieve: Checking prime: " << candidate; + while (*in.get() > primes.back()) { + candidate += 2; + bool prime = true; + for (auto i : primes) { + if(candidate % i == 0) { + // Candidate is not prime. Break and add 2 by starting the loop again + prime = false; + break; + } + } + // If the candidate is not divisible by any prime in the list, it is prime + if (prime) { + primes.push_back(candidate); + reactor::log::Info() << "Sieve: Found prime: " << candidate; + } + } + + // We are now assured that the input is less than or + // equal to the last prime on the list. + // See whether the input is an already found prime. + // Search the primes from the end, where they are sparser. + for (auto i = primes.rbegin(); i != primes.rend(); ++i) { + if(*i == *in.get()) { + out.set(true); + return; + } + } + =} +} + +reactor Destination { + input ok:bool; + input in:int; + state last_invoked:{=reactor::TimePoint=}; + reaction(ok, in) {= + if (ok.is_present() && in.is_present()) { + reactor::log::Info() << "Destination: Input " << *in.get() << " is a prime at logical time ( " + << get_elapsed_logical_time() << " )"; + } + if( get_logical_time() <= last_invoked) { + reactor::log::Error() << "Invoked at logical time (" << get_logical_time() << ") " + << "but previously invoked at logical time (" << get_elapsed_logical_time() << ")"; + } + + last_invoked = get_logical_time(); + =} +} +main reactor { + source = new Source(); + sieve = new Sieve(); + destination = new Destination(); + source.out -> sieve.in; + sieve.out -> destination.ok; + source.out -> destination.in; +} \ No newline at end of file diff --git a/test/Cpp/src/CompositionGain.lf b/test/Cpp/src/CompositionGain.lf new file mode 100644 index 0000000000..b37208ae42 --- /dev/null +++ b/test/Cpp/src/CompositionGain.lf @@ -0,0 +1,30 @@ +// This tests send data through a contained reactor +target Cpp; +reactor Gain { + input gainin:int; + output y:int; + reaction(gainin) -> y {= + reactor::log::Info() << "Gain received " << *gainin.get(); + y.set(*gainin.get()*2); + =} +} +reactor Wrapper { + input x:int; + output y:int; + gain = new Gain(); + x -> gain.gainin; + gain.y -> y; +} +main reactor CompositionGain { + wrapper = new Wrapper(); + reaction(startup) -> wrapper.x {= + wrapper.x.set(42); + =} + reaction(wrapper.y) {= + reactor::log::Info() << "Received " << *wrapper.y.get(); + if (*wrapper.y.get() != 42*2) { + reactor::log::Error() << "Received value should have been " << 42 * 2; + exit(2); + } + =} +} \ No newline at end of file diff --git a/test/Cpp/src/DoubleInvocation.lf b/test/Cpp/src/DoubleInvocation.lf new file mode 100644 index 0000000000..25d8e2e0b7 --- /dev/null +++ b/test/Cpp/src/DoubleInvocation.lf @@ -0,0 +1,54 @@ +// This illustrates a very strange bug that showed up +// and has now been fixed. This test ensures it does +// not reappear. +// At logical time zero, the two Print reactors used to be +// fired twice each at the same logical time. +// They should only be fired once. +// This behavior was oddly eliminated by either of the following +// actions, neither of which should affect this behavior: +// * Removing the startup reaction in Print. +// * Sending only position, not velocity from Ball. +// (copied from the c version of the test) + +target Cpp{ + timeout: 5 sec, + fast: true +} +reactor Ball { + output position:int; + output velocity:int; + state p:int(200); + timer trigger(0, 1 sec); + reaction(trigger) -> position, velocity {= + position.set(p); + velocity.set(-1); + p -= 1; + =} +} +reactor Print { + input velocity:int; + input position:int; + state previous:int(-1); + reaction (startup) {= + reactor::log::Info() << "####### Print startup"; + =} + reaction (position, velocity) {= + if (position.is_present()) { + reactor::log::Info() << "Position: " << *position.get(); + } + if (*position.get() == previous) { + reactor::log::Error() << "Multiple firings at the same logical time!"; + exit(1); + } + =} + +} +main reactor DoubleInvocation { + b1 = new Ball(); + p = new Print(); + plot = new Print(); + b1.position -> p.position; + b1.velocity -> p.velocity; + b1.position -> plot.position; + b1.velocity -> plot.velocity; +} \ No newline at end of file diff --git a/test/Cpp/src/DoublePort.lf b/test/Cpp/src/DoublePort.lf new file mode 100644 index 0000000000..9751cbabcf --- /dev/null +++ b/test/Cpp/src/DoublePort.lf @@ -0,0 +1,61 @@ +/** + * Test the case where two upstream reactors + * pass messages to a downstream reactor on two + * different ports. One message carries + * a microstep delay relative to the other. + * + * @author Maiko Brants + */ +target Cpp { + timeout: 900 msec, + fast: true +}; + +import Count from "lib/Count.lf"; + +reactor CountMicrostep { + state count:int(1); + output out:int; + logical action act:int; + timer t(0, 1 sec); + reaction(t) -> act {= + act.schedule( count); + count++; + =} + + reaction(act) -> out {= + out.set(act.get()); + =} +} + +reactor Print { + input in:int; + input in2:int; + reaction(in, in2) {= + if(in.is_present()){ + reactor::log::Info() << "At tag (" << get_elapsed_logical_time() << ", " << environment()->logical_time().micro_step() + << "), received in = " << *in.get(); + } else if (in2.is_present()){ + reactor::log::Info() << "At tag (" << get_elapsed_logical_time() << ", " << environment()->logical_time().micro_step() + << "), received in2 = " << *in2.get(); + } + + + if ( in.is_present() && in2.is_present()) { + reactor::log::Error() << "ERROR: invalid logical simultaneity."; + exit(1); + } + =} + + reaction(shutdown) {= + reactor::log::Info() << "SUCCESS: messages were at least one microstep apart."; + =} +} + +main reactor DoublePort { + c = new Count(); + cm = new CountMicrostep(); + p = new Print(); + c.c -> p.in; + cm.out -> p.in2; +} \ No newline at end of file diff --git a/test/Cpp/src/ImportComposition.lf b/test/Cpp/src/ImportComposition.lf new file mode 100644 index 0000000000..a62e221dce --- /dev/null +++ b/test/Cpp/src/ImportComposition.lf @@ -0,0 +1,42 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* This tests the ability to import a reactor definition +* that itself imports a reactor definition. +* +* modeled after the C version of this test +**/ +target Cpp; +import ImportedComposition from "lib/ImportedComposition.lf"; + +main reactor ImportComposition { + public preamble {= + #include "reactor-cpp/logging.hh" + =} + + imp_comp = new ImportedComposition(); + state received:bool(false); + reaction(startup) -> imp_comp.x {= + imp_comp.x.set(42); + =} + reaction(imp_comp.y) {= + auto receive_time = get_elapsed_logical_time(); + reactor::log::Info() << "Received " << *imp_comp.y.get() << " at time " << receive_time; + received = true; + if(receive_time != 55ms) { + reactor::log::Error() << "ERROR: Received time should have been: 55,000,000."; + exit(1); + } + if(*imp_comp.y.get() != 42*2*2) { + reactor::log::Error() << "ERROR: Received value should have been: " << 42*2*2 << "."; + exit(2); + } + =} + reaction(shutdown) {= + if(!received){ + reactor::log::Error() << "ERROR: Nothing received."; + exit(3); + } + =} +} \ No newline at end of file diff --git a/test/Cpp/src/ImportRenamed.lf b/test/Cpp/src/ImportRenamed.lf new file mode 100644 index 0000000000..f66864b1ed --- /dev/null +++ b/test/Cpp/src/ImportRenamed.lf @@ -0,0 +1,23 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* This tests the ability to import a reactor definition +* that itself imports a reactor definition. +* +* modeled after the C version of this test +**/ +target Cpp; +import Imported as X from "lib/Imported.lf" +import Imported as Y from "lib/Imported.lf" +import ImportedAgain as Z from "lib/ImportedAgain.lf" +main reactor { + timer t; + a = new X(); + b = new Y(); + c = new Z(); + + reaction(t) -> a.x {= + a.x.set(42); + =} +} \ No newline at end of file diff --git a/test/Cpp/src/ParameterHierarchy.lf b/test/Cpp/src/ParameterHierarchy.lf new file mode 100644 index 0000000000..9d094ca699 --- /dev/null +++ b/test/Cpp/src/ParameterHierarchy.lf @@ -0,0 +1,28 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* Test that parameter values pass down a deep hierarchy. +* +* modeled after the C version of this test +**/ +target Cpp; +reactor Deep(p:int(0)) { + reaction(startup) {= + if(p != 42) { + reactor::log::Error() << "Parameter value is: " << p << ". Should have been 42."; + exit(1); + } else { + reactor::log::Info() << "Success."; + } + =} +} +reactor Intermediate(p:int(10)) { + a = new Deep(p = p); +} +reactor Another(p:int(20)) { + a = new Intermediate(p = p); +} +main reactor ParameterHierarchy { + a = new Intermediate(p = 42); +} \ No newline at end of file diff --git a/test/Cpp/src/ScheduleLogicalAction.lf b/test/Cpp/src/ScheduleLogicalAction.lf new file mode 100644 index 0000000000..2b38e2620a --- /dev/null +++ b/test/Cpp/src/ScheduleLogicalAction.lf @@ -0,0 +1,51 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* This checks that a logical action is scheduled the specified +* logical time after the current logical time. +* +* Modeled after the C version of this test. +**/ +target Cpp { + fast: true, + timeout: 3 sec +}; +reactor foo { + input x:int; + output y:int; + logical action a:void; + reaction(x) -> y, a {= + y.set( 2*(*x.get())); + // The following uses physical time, incorrectly. + a.schedule(500ms); + =} + reaction(a) -> y {= + y.set(-42); + =} +} + +reactor print { + state expected_time:time(0); + input x:int; + reaction(x) {= + auto elapsed_time = get_elapsed_logical_time(); + reactor::log::Info() << "Result is " << *x.get(); + reactor::log::Info() << "Current logical time is: " << elapsed_time; + reactor::log::Info() << "Current physical time is: " << get_elapsed_physical_time().count(); + if(elapsed_time != expected_time) { + reactor::log::Error() << "ERROR: Expected logical time to be " << expected_time; + exit(1); + } + expected_time += 500ms; + =} +} +main reactor { + f = new foo(); + p = new print(); + timer t(0, 1 sec); + reaction(t) -> f.x {= + f.x.set(42); + =} + f.y -> p.x; +} \ No newline at end of file diff --git a/test/Cpp/src/SelfLoop.lf b/test/Cpp/src/SelfLoop.lf new file mode 100644 index 0000000000..9a7ee29dbd --- /dev/null +++ b/test/Cpp/src/SelfLoop.lf @@ -0,0 +1,42 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* Modeled after the C version of this test. +**/ +target Cpp { + timeout: 1 sec, + fast: true +}; +reactor Self { + input x:int; + output y:int; + logical action a:int; + state expected:int(43); + reaction(a) -> y {= + reactor::log::Info() << "a = " << *a.get(); + y.set(*a.get()+1); + =} + reaction(x) -> a {= + reactor::log::Info() << "x = " << *x.get(); + if(*x.get() != expected){ + reactor::log::Error() << "Expected " << expected; + exit(1); + } + expected++; + a.schedule(x.get(), 100ms); + =} + reaction(startup) -> a {= + a.schedule(42, 0ns); + =} + reaction(shutdown) {= + if(expected <= 43) { + reactor::log::Error() << "Received no data."; + exit(2); + } + =} +} +main reactor SelfLoop { + u = new Self(); + u.y -> u.x; +} \ No newline at end of file diff --git a/test/Cpp/src/SlowingClock.lf b/test/Cpp/src/SlowingClock.lf new file mode 100644 index 0000000000..ca271ada9d --- /dev/null +++ b/test/Cpp/src/SlowingClock.lf @@ -0,0 +1,42 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* Events are scheduled with increasing additional delays of 0, 100, 300, 600 +* msec on a logical action with a minimum delay of 100 msec. +* The use of the logical action ensures the elapsed time jumps exactly from +* 0 to 100, 300, 600, and 1000 msec. +* +* Modeled after the C version of this test. +**/ +target Cpp { + timeout: 1 sec, + fast: true, +}; +main reactor SlowingClock { + logical action a(100 msec); + state interval:time(100 msec); + state expected_time:time(100 msec); + reaction(startup) -> a {= + a.schedule(0ns); + =} + reaction(a) -> a {= + auto elapsed_logical_time = get_elapsed_logical_time(); + reactor::log::Info() << "Logical time since start: " << elapsed_logical_time; + if(elapsed_logical_time != expected_time) { + reactor::log::Error() << "Expected time to be: " << expected_time; + exit(1); + } + a.schedule(interval); + expected_time += 100ms + interval; + interval += 100ms; + =} + reaction(shutdown) {= + if(expected_time != 1500ms){ + reactor::log::Error() << "Expected the next expected time to be: 1500000000 nsec."; + reactor::log::Error() << "It was: " << expected_time; + } else { + reactor::log::Info() << "Test passes."; + } + =} +} diff --git a/test/Cpp/src/SlowingClockPhysical.lf b/test/Cpp/src/SlowingClockPhysical.lf new file mode 100644 index 0000000000..c9c9ec15f7 --- /dev/null +++ b/test/Cpp/src/SlowingClockPhysical.lf @@ -0,0 +1,43 @@ +/** +* +* @author Maiko Brants TU Dresden +* + * Events are scheduled with increasing additional delays of 0, 100, 300, 600 + * msec on a physical action with a minimum delay of 100 msec. + * The use of the physical action makes the elapsed time jumps from 0 to + * approximately 100 msec, to approximatly 300 msec thereafter, drifting away + * further with each new event. +* +* Modeled after the C version of this test. +**/ +target Cpp { + timeout: 1500 msec +}; +main reactor SlowingClockPhysical { + physical action a; + state interval:time(100 msec); + state expected_time:time(100 msec); + reaction(startup) -> a {= + expected_time=100ms; + a.schedule(100ms); + =} + reaction(a) -> a {= + auto elapsed_logical_time{get_elapsed_logical_time()}; + reactor::log::Info() << "Logical time since start: " << elapsed_logical_time; + if(elapsed_logical_time < expected_time){ + reactor::log::Error() << "Expected logical time to be at least: " << expected_time; + exit(1); + } + interval += 100ms; + expected_time = 100ms + interval; + a.schedule(interval); + reactor::log::Info() << "Scheduling next to occur approximately after: " << interval; + =} + reaction(shutdown) {= + if(expected_time < 500ms){ + reactor::log::Error() << "Expected the next expected time to be at least: 500000000 nsec."; + reactor::log::Error() << "It was: " << expected_time; + exit(2); + } + =} +} \ No newline at end of file diff --git a/test/Cpp/src/StartupOutFromInside.lf b/test/Cpp/src/StartupOutFromInside.lf new file mode 100644 index 0000000000..219c9cc703 --- /dev/null +++ b/test/Cpp/src/StartupOutFromInside.lf @@ -0,0 +1,25 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* Modeled after the C version of this test. +**/ +target Cpp; + +reactor Bar { + output out:int; + reaction(startup) -> out {= + out.set(42); + =} +} + +main reactor StartupOutFromInside { + bar = new Bar(); + reaction(startup) bar.out {= + reactor::log::Info() << "Output from bar: " << *bar.out.get(); + if(*bar.out.get() != 42) { + reactor::log::Error() << "Expected 42!"; + exit(1); + } + =} +} \ No newline at end of file diff --git a/test/Cpp/src/Timeout_Test.lf b/test/Cpp/src/Timeout_Test.lf new file mode 100644 index 0000000000..1ec45272ca --- /dev/null +++ b/test/Cpp/src/Timeout_Test.lf @@ -0,0 +1,44 @@ +/** +* A test for the timeout functionality in Lingua Franca. +* +* @author Maiko Brants TU Dresden +* +* Modeled after the C version of this test. +**/ +target Cpp{ + timeout: 11 msec +} + +import Sender from "lib/LoopedActionSender.lf" + +reactor Consumer { + input in:int; + state success:bool(false); + reaction(in) {= + auto current{get_elapsed_logical_time()}; + if(current > 11ms ){ + reactor::log::Error() << "Received at: " << current.count() << ". Failed to enforce timeout."; + exit(1); + } else if(current == 11ms) { + success=true; + } + =} + + reaction(shutdown) {= + reactor::log::Info() << "Shutdown invoked at " << get_elapsed_logical_time(); + if((get_elapsed_logical_time() == 11ms ) && (success == true)){ + reactor::log::Info() << "SUCCESS: successfully enforced timeout."; + } else { + reactor::log::Error() << "Shutdown invoked at: " << get_elapsed_logical_time() << ". Failed to enforce timeout."; + exit(1); + } + + =} +} + +main reactor Timeout_Test { + consumer = new Consumer(); + producer = new Sender(break_interval = 1 msec); + + producer.out -> consumer.in; +} \ No newline at end of file diff --git a/test/Cpp/src/ToReactionNested.lf b/test/Cpp/src/ToReactionNested.lf new file mode 100644 index 0000000000..dbacf94b67 --- /dev/null +++ b/test/Cpp/src/ToReactionNested.lf @@ -0,0 +1,36 @@ +target Cpp { + timeout: 5 sec, + fast: true +}; + +import Count from "lib/Count.lf"; + +reactor CountContainer { + output out:int; + c1 = new Count(); + c1.c -> out; +} + +main reactor { + state count:int(1); + state received:bool(false); + + s = new CountContainer(); + + reaction(s.out) {= + if (s.out.is_present()){ + reactor::log::Info() << "Received " << *s.out.get(); + if(count != *s.out.get()){ + reactor::log::Error() << "Expected " << count; + } + received = true; + } + count++; + =} + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "No inputs present."; + exit(1); + } + =} +} \ No newline at end of file diff --git a/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf b/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf new file mode 100644 index 0000000000..5a443d28c3 --- /dev/null +++ b/test/Cpp/src/TriggerDownstreamOnlyIfPresent2.lf @@ -0,0 +1,38 @@ +/** +* This test checks that a downstream reaction is triggered only if its trigger is present. +* +* @author Maiko Brants TU Dresden +* +* Modeled after the C version of this test. +**/ +target Cpp { + timeout: 1 sec, + fast: true +}; +reactor Source { + output[2] out:int; + state count:int(0); + timer t(0, 200 msec); + reaction(t) -> out {= + if(count++ % 2 == 0) { + out[0].set(count); + } else { + out[1].set(count); + } + =} +} +reactor Destination { + input in:int; + reaction(in) {= + if(!in.is_present()){ + reactor::log::Error() << "Reaction to input of triggered even though all inputs are absent!"; + exit(1); + } + =} +} + +main reactor TriggerDownstreamOnlyIfPresent2 { + s = new Source(); + d = new[2] Destination(); + s.out -> d.in; +} \ No newline at end of file diff --git a/test/Cpp/src/lib/ImportedComposition.lf b/test/Cpp/src/lib/ImportedComposition.lf new file mode 100644 index 0000000000..67902fc9c9 --- /dev/null +++ b/test/Cpp/src/lib/ImportedComposition.lf @@ -0,0 +1,27 @@ +/** +* +* @author Maiko Brants TU Dresden +* +* This is used by the test for the ability to import a reactor definition +* that itself imports a reactor definition. +* +* modeled after the C version of this test +**/ +target Cpp; + +reactor Gain { + input x:int; + output y:int; + reaction(x) -> y {= + y.set(*x.get() * 2); + =} +} +reactor ImportedComposition { + input x:int; + output y:int; + g1 = new Gain(); + g2 = new Gain(); + x -> g1.x after 10 msec; + g1.y -> g2.x after 30 msec; + g2.y -> y after 15 msec; +} \ No newline at end of file diff --git a/test/Cpp/src/lib/LoopedActionSender.lf b/test/Cpp/src/lib/LoopedActionSender.lf new file mode 100644 index 0000000000..0611f429d3 --- /dev/null +++ b/test/Cpp/src/lib/LoopedActionSender.lf @@ -0,0 +1,32 @@ +/** + * A sender reactor that outputs integers + * in superdense time. + * + * Modeled after the C version of this file. + * + * @author Maiko Brants + */ + target Cpp; + + /** + * @param take_a_break_after: Indicates how many messages are sent + * in consecutive superdense time + * @param break_interval: Determines how long the reactor should take + * a break after sending take_a_break_after messages. + */ + reactor Sender(take_a_break_after:int(10), break_interval:time(400 msec)) { + output out:int; + logical action act; + state sent_messages:int(0); + reaction(startup, act) -> act, out {= + out.set(sent_messages); + sent_messages++; + if(sent_messages < take_a_break_after){ + act.schedule(0ns); + } else { + // Take a break + sent_messages=0; + act.schedule(break_interval); + } + =} + } \ No newline at end of file