Jelajahi Sumber

Controller and test framework can deal with bags of (simultaneous) input events. Add examples 19, 20 and 21 from Day & Atlee.

Joeri Exelmans 5 tahun lalu
induk
melakukan
33969e5928

+ 10 - 7
src/sccd/controller/controller.py

@@ -20,8 +20,8 @@ class Controller:
 
     @dataclasses.dataclass(eq=False, frozen=True)
     class EventQueueEntry:
-        __slots__ = ["event", "targets"]
-        event: InternalEvent
+        __slots__ = ["events", "targets"]
+        events: List[InternalEvent]
         targets: List[Instance]
 
         def __repr__(self):
@@ -36,7 +36,7 @@ class Controller:
         # Our instances should not have 'full access' to the global event queue (e.g. they shouldn't pop due events, that's the controller's task!). They are only allowed to schedule and cancel scheduled events. Hence we pass them 2 callbacks:
 
         def schedule_after(after, event, instances):
-            entry = Controller.EventQueueEntry(event, instances)
+            entry = Controller.EventQueueEntry([event], instances)
             return self.queue.add(self.simulated_time + after, entry)
 
         def cancel_after(entry):
@@ -55,8 +55,8 @@ class Controller:
 
     # Lower-level way of adding an event to the queue
     # See also method 'add_input'
-    def schedule(self, timestamp: int, event: InternalEvent, instances: List[Instance]):
-        self.queue.add(timestamp, Controller.EventQueueEntry(event, instances))
+    def schedule(self, timestamp: int, events: List[InternalEvent], instances: List[Instance]):
+        self.queue.add(timestamp, Controller.EventQueueEntry(events, instances))
 
     # Low-level utility function, intended to map a port name to a list of instances
     # For now, all known ports map to all instances (i.e. all ports are broadcast ports)
@@ -71,6 +71,9 @@ class Controller:
         # TODO: multicast event only to instances that subscribe to this port.
         return self.object_manager.instances
 
+    def all_instances(self) -> List[Instance]:
+        return self.object_manager.instances
+
     # Higher-level way of adding an event to the queue.
     # See also method 'schedule'
     def add_input(self, timestamp: int, port: str, event_name: str, params = []):
@@ -82,7 +85,7 @@ class Controller:
         instances = self.inport_to_instances(port)
         event = InternalEvent(event_id, event_name, params)
 
-        self.schedule(timestamp, event, instances)
+        self.schedule(timestamp, [event], instances)
 
     # Get timestamp of earliest entry in event queue
     def next_wakeup(self) -> Optional[int]:
@@ -120,5 +123,5 @@ class Controller:
             # print("remaining", self.queue)
             # run all instances for whom there are events
             for instance in entry.targets:
-                instance.big_step([entry.event])
+                instance.big_step(entry.events)
                 # print_debug("completed big step (time = %s)" % str(self.cd.globals.delta * self.simulated_time))

+ 4 - 1
src/sccd/statechart/dynamic/round.py

@@ -55,7 +55,7 @@ class Round(ABC):
         else:
             return self.remainder_events
 
-    def __repr__(self):
+    def __str__(self):
         return self.name
 
 class SuperRoundMaximality(ABC):
@@ -93,6 +93,9 @@ class SuperRound(Round):
         self.maximality = maximality
         self.limit = limit
 
+    def __str__(self):
+        return self.name + " > " + str(self.subround)
+
     def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         arenas_changed = Bitmap()
         arenas_stabilized = Bitmap()

+ 3 - 0
src/sccd/statechart/dynamic/statechart_instance.py

@@ -66,6 +66,9 @@ class StatechartInstance(Instance):
 
 
         if semantics.big_step_maximality == Maximality.TAKE_ONE:
+            # If Big-Step Maximality is Take One, we disable combo steps.
+            # This is not entirely according to the BSML spec, but in 99% of cases, this is what you'd want.
+            # If we did not do this, we would have to allow the user to explicitly disable combo-steps instead.
             self._big_step = combo_step = SuperRound(termcolor.colored("big_one", 'red'), subround=small_step, maximality=TakeOne()) # No combo steps
 
         elif semantics.big_step_maximality == Maximality.TAKE_MANY or semantics.big_step_maximality == Maximality.SYNTACTIC:

+ 5 - 2
src/sccd/test/run.py

@@ -54,8 +54,11 @@ class Test(unittest.TestCase):
 
         controller = Controller(test.cd, on_output)
 
-        for i in test.input:
-          controller.schedule(i.timestamp.eval(None), i.event, controller.inport_to_instances(i.port))
+        for bag in test.input:
+          controller.schedule(
+            bag.timestamp.eval(None),
+            bag.events,
+            controller.all_instances()) # broadcast input events to all instances
 
         def controller_thread():
           try:

+ 41 - 19
src/sccd/test/xml.py

@@ -6,16 +6,15 @@ from sccd.cd.static.cd import *
 _empty_scope = Scope("test", parent=None)
 
 @dataclass
-class TestInputEvent:
-  event: InternalEvent
-  port: str
+class TestInputBag:
+  events: List[InternalEvent]
   timestamp: Expression
 
 @dataclass
 class TestVariant:
   name: str
   cd: AbstractCD
-  input: List[TestInputEvent]
+  input: List[TestInputBag]
   output: List[List[OutputEvent]]
 
 def test_parser_rules(statechart_parser_rules):
@@ -25,29 +24,52 @@ def test_parser_rules(statechart_parser_rules):
 
   def parse_test(el):
     def parse_input(el):
-      def parse_input_event(el):
-        name = require_attribute(el, "name")
-        port = require_attribute(el, "port")
-        time = require_attribute(el, "time")
-        time_expr = parse_expression(globals, time)
-        time_type = time_expr.init_expr(scope=_empty_scope)
-        check_duration_type(time_type)
-        params = []
-        event_id = globals.events.get_id(name)
-        input.append(TestInputEvent(
-          event=InternalEvent(id=event_id, name=name, params=params),
-          port=port,
-          timestamp=time_expr))
 
+      def param_parser_rules():
+        params = []
         def parse_param(el):
           text = require_attribute(el, "expr")
           expr = parse_expression(globals, text)
           expr.init_expr(scope=_empty_scope)
           params.append(expr.eval(memory=None))
+        return (params, parse_param)
 
-        return [("param*", parse_param)]
+      def parse_time(time: str) -> Expression:
+        expr = parse_expression(globals, time)
+        type = expr.init_expr(scope=_empty_scope)
+        check_duration_type(type)
+        return expr
+
+      def make_input_event(name: str, params):
+        event_id = globals.events.get_id(name)
+        return InternalEvent(id=event_id, name=name, params=params)
+
+      def parse_input_event(el):
+        # port = require_attribute(el, "port")
+        name = require_attribute(el, "name")
+        time = require_attribute(el, "time")
+        time_expr = parse_time(time)
+        params, params_parser = param_parser_rules()
+        input.append(TestInputBag(
+          events=[make_input_event(name, params)],
+          timestamp=time_expr))
+        return {"param": params_parser}
+
+      def parse_bag(el):
+        # bag of (simultaneous) input events
+        time = require_attribute(el, "time")
+        time_expr = parse_time(time)
+        events = []
+        input.append(TestInputBag(events, time_expr))
+        def parse_bag_event(el):
+          # port = require_attribute(el, "port")
+          name = require_attribute(el, "name")
+          params, params_parser = param_parser_rules()
+          events.append(make_input_event(name, params))
+          return {"param": params_parser}
+        return {"event": parse_bag_event}
 
-      return [("event+", parse_input_event)]
+      return {"event": parse_input_event, "bag": parse_bag}
 
     def parse_output(el):
       def parse_big_step(el):

+ 23 - 0
test/test_files/day_atlee/fail_22_invar_combo_many.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" ?>
+<test>
+  <!-- fails because of race condition -->
+  <statechart src="statechart_fig20_invar.xml">
+    <override_semantics
+      big_step_maximality="take_many"
+      combo_step_maximality="take_many"
+      assignment_memory_protocol="combo_step"/>
+  </statechart>
+
+  <input>
+    <event port="in" name="start" time="0 d"/>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="done">
+        <param val="21"/>
+        <param val="16"/>
+      </event>
+    </big_step>
+  </output>
+</test>

+ 1 - 1
test/test_files/day_atlee/test_13_invar_rhs_smallstep.xml

@@ -12,7 +12,7 @@
   <output>
     <big_step>
       <!-- BSML paper gives example of big step t1, t2, t3, t4.
-           Due to fairness, we instead take the big step t1, t3, t2, t4,
+           Due to "always on"-fairness, we instead take the big step t1, t3, t2, t4,
            giving the following values for a,b: -->
       <event port="out" name="done">
         <param val="33"/>

+ 1 - 0
test/test_files/day_atlee/test_19_swaptwice_combo.xml

@@ -1,4 +1,5 @@
 <test>
+  <!-- Example 19. Statechart from Figure 21, extended with an input event to set the variables a,b, and with an output event to retreive the values (because currently, testing is black-box) -->
   <statechart>
     <semantics
       big_step_maximality="take_many"

+ 87 - 0
test/test_files/day_atlee/test_20_chemplant_combo.xml

@@ -0,0 +1,87 @@
+<test>
+  <statechart>
+    <semantics
+      big_step_maximality="take_many"
+      combo_step_maximality="take_one"
+      input_event_lifeline="first_combo_step"
+      internal_event_lifeline="next_combo_step"
+      enabledness_memory_protocol="small_step"
+      assignment_memory_protocol="small_step"
+    />
+    
+    <datamodel>
+      a = 0;
+      b = 0;
+    </datamodel>
+
+    <inport name="in">
+      <event name="inc_one"/>
+      <event name="inc_two"/>
+    </inport>
+
+    <outport name="out">
+      <event name="start_process"/>
+    </outport>
+
+    <root>
+      <parallel id="Plant">
+        <state id="Process_1" initial="Idle_1">
+          <state id="Idle_1">
+            <transition port="in" event="inc_one" target="../Wait_1">
+              <code> a += 1; b += 1; </code>
+              <raise event="process"/>
+            </transition>
+          </state>
+          <state id="Wait_1">
+            <transition port="in" event="end_process" target="../Idle_1"/>
+          </state>
+        </state>
+
+        <state id="Process_2" initial="Idle_2">
+          <state id="Idle_2">
+            <transition port="in" event="inc_two" target="../Wait_2">
+              <code> a += 2; b += 2; </code>
+              <raise event="process"/>
+            </transition>
+          </state>
+          <state id="Wait_2">
+            <transition port="in" event="end_process" target="../Idle_2"/>
+          </state>
+        </state>
+
+        <state id="Controller" initial="Idle">
+          <state id="Idle">
+            <transition event="process" target="../Wait">
+              <raise event="start_process">
+                <param expr="a"/>
+                <param expr="b"/>
+              </raise>
+            </transition>
+          </state>
+          <state id="Wait">
+            <transition port="in" event="end_process" target="../Idle">
+              <code> a = 0; b = 0; </code>
+            </transition>
+          </state>
+        </state>
+      </parallel>
+    </root>
+  </statechart>
+
+  <input>
+    <bag time="0 d">
+      <!-- 2 simultaneous input events -->
+      <event port="in" name="inc_one"/>
+      <event port="in" name="inc_two"/>
+    </bag>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="start_process">
+        <param val="3"/>
+        <param val="3"/>
+      </event>
+    </big_step>
+  </output>
+</test>

+ 27 - 0
test/test_files/day_atlee/test_21_counter_combo.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" ?>
+<test>
+  <!-- Same as Example 6, but with combo steps :)
+  
+   We deviate a bit from Example 21 by using big-step maximality: Take Many,
+   because in SCCD, if Big-Step Maximality is "Take One", combo steps are disabled. -->
+  <statechart src="statechart_fig8_counter.xml">
+    <override_semantics
+      big_step_maximality="take_many"
+      combo_step_maximality="take_one"
+      input_event_lifeline="first_combo_step"
+      internal_event_lifeline="next_combo_step"/>
+  </statechart>
+
+  <input>
+    <event port="in" name="tk0" time="0 d"/>
+    <event port="in" name="tk0" time="0 d"/>
+    <event port="in" name="tk0" time="0 d"/>
+    <event port="in" name="tk0" time="0 d"/>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="done"/>
+    </big_step>
+  </output>
+</test>

+ 22 - 0
test/test_files/day_atlee/test_22_invar_combo_one.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" ?>
+<test>
+  <statechart src="statechart_fig20_invar.xml">
+    <override_semantics
+      big_step_maximality="take_many"
+      combo_step_maximality="take_one"
+      assignment_memory_protocol="combo_step"/>
+  </statechart>
+
+  <input>
+    <event port="in" name="start" time="0 d"/>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="done">
+        <param val="27"/>
+        <param val="22"/>
+      </event>
+    </big_step>
+  </output>
+</test>