diff --git a/xCAT-test/blackboxtest/src/blackboxtest_lib/dhcp.py b/xCAT-test/blackboxtest/src/blackboxtest_lib/dhcp.py index 55102f3c7..d6fa1120d 100644 --- a/xCAT-test/blackboxtest/src/blackboxtest_lib/dhcp.py +++ b/xCAT-test/blackboxtest/src/blackboxtest_lib/dhcp.py @@ -228,8 +228,9 @@ class Wire(object): def send(self, frame): self.socket.send(frame) - def collect(self, session, deadline): - """Every reply to this session's xid until `deadline`. + def collect(self, session, deadline, stop=()): + """Every reply to this session's xid until `deadline`, or until a + reply of a type in `stop` arrives. A reply of an unexpected type is kept, so a NAK where an ACK was wanted is reported as a NAK rather than as a timeout. @@ -237,7 +238,8 @@ class Wire(object): found = [] while True: remaining = deadline - time.monotonic() - if remaining <= 0 or not select.select([self.socket], [], [], remaining)[0]: + if remaining <= 0 or any(r.fields["msgtype"] in stop for r in found) \ + or not select.select([self.socket], [], [], remaining)[0]: return found frame = self.socket.recv(2048) if frame is None: @@ -293,7 +295,8 @@ def run_step(step, session, context, timeout, retries): sent = describe(step, session, trans, context) session.wire.send(build_frame(step, session, trans, context, session.wire.scapy)) - replies = session.wire.collect(session, time.monotonic() + timeout) + replies = session.wire.collect(session, time.monotonic() + timeout, + stop_on(step, wanted)) if any(r.fields["msgtype"] in wanted for r in replies) \ or (replies and not wanted): break @@ -328,6 +331,18 @@ def run_step(step, session, context, timeout, retries): return chosen, extras, sent +def stop_on(step, wanted): + """The reply types that end a step's listening window early. + + A step that counts `offers` must hear every server, so it listens for the + whole timeout. So does `expect = none`, where silence is the result. + """ + if any("offers" in (a.target, a.value) + for a in step.assertions): + return frozenset() + return wanted + + def distinct_servers(replies): """One reply per answering server. diff --git a/xCAT-test/blackboxtest/tests/test_dhcp.py b/xCAT-test/blackboxtest/tests/test_dhcp.py index 4ddd4c86a..05ef66bfa 100644 --- a/xCAT-test/blackboxtest/tests/test_dhcp.py +++ b/xCAT-test/blackboxtest/tests/test_dhcp.py @@ -164,7 +164,8 @@ class FakeWire(object): def send(self, frame): self.sent += 1 - def collect(self, session, deadline): + def collect(self, session, deadline, stop=()): + self.stop = stop return self.answers.pop(0) if self.answers else [] def close(self): @@ -239,6 +240,16 @@ class StepLoop(unittest.TestCase): self.assertEqual(extras["met"], met) self.assertEqual(session.state, dhcp.INIT) + def test_a_step_stops_listening_once_its_reply_arrived(self): + # Unless it counts the servers that answered, or proves silence. + cases = (("", {"OFFER"}), ("assert =\n offers == 1\n", set()), + ("expect = none\n", set())) + for extra, stop in cases: + step, = self.step("[step d]\ntype = discover\n" + extra) + session = dhcp.Session(FakeWire([reply("OFFER")])) + dhcp.run_step(step, session, subst.Context(), 0.1, 1) + self.assertEqual(set(session.wire.stop), stop, extra) + def test_a_step_mac_replaces_the_random_one(self): step, = self.step("[step d]\ntype = discover\nmac = 02-00-00-00-00-AA\n") session = dhcp.Session(FakeWire())