extends TestCase ## Two NetLinks in one process, connected over the loopback interface. ## Events per link, collected while frames pass. var _events: Dictionary[NetLink, Array] = {} ## Polls `links` for `frames` frames and collects their events. func _pump(links: Array[NetLink], frames: int) -> void: for frame in frames: for link in links: if not _events.has(link): _events[link] = [] _events[link].append_array(link.poll(1.0 / 60.0)) await tree.process_frame func _types(link: NetLink) -> Array: return _events.get(link, []).map(func(event: Dictionary) -> String: return event["type"]) func _first(link: NetLink, type: String) -> Dictionary: for event: Dictionary in _events.get(link, []): if event["type"] == type: return event return {} ## The bytes of every event of `type` that `link` got, each as an Array. func _received(link: NetLink, type: String) -> Array: return _events.get(link, []).filter(func(event: Dictionary) -> bool: return event["type"] == type).map( func(event: Dictionary) -> Array: return Array(event["bytes"]) ) ## A host and a guest that have connected. func _connected_pair() -> Array[NetLink]: var host := NetLink.new() check_eq(host.host(0, "127.0.0.1"), OK, "the host listens") check(host.local_port() > 0, "on a port the system chose") var guest := NetLink.new() check_eq(guest.join("127.0.0.1", host.local_port()), OK, "the guest starts connecting") await _pump([host, guest], 10) return [host, guest] func test_a_guest_connects_and_both_sides_hear_of_it() -> void: var links := await _connected_pair() var host := links[0] var guest := links[1] check(guest.is_connected_to_peers(), "the guest is connected") check_eq(_first(guest, "connected").get("peer"), GwSession.HOST_PEER, "the guest sees the host as peer 1") check(_first(host, "connected").get("peer", 0) > 1, "the host sees the guest") host.close() guest.close() func test_messages_arrive_in_order_on_both_channels() -> void: var links := await _connected_pair() var host := links[0] var guest := links[1] var guest_peer: int = _first(host, "connected")["peer"] _events.clear() var big := PackedByteArray() big.resize(50000) big.fill(7) host.send(guest_peer, big) host.send(guest_peer, PackedByteArray([1])) guest.send(GwSession.HOST_PEER, PackedByteArray([2, 3])) guest.send_figure(GwSession.HOST_PEER, PackedByteArray([4])) await _pump([host, guest], 20) var received := _events[guest].filter(func(event: Dictionary) -> bool: return event["type"] == "message") check_eq(received.size(), 2, "two messages for the guest") if received.size() == 2: check_eq(received[0]["bytes"].size(), 50000, "the big one first") check_eq(received[1]["bytes"], PackedByteArray([1]), "then the small one") check_eq(_first(host, "message").get("bytes"), PackedByteArray([2, 3]), "the host got the guest's message") check_eq(_first(host, "figure").get("bytes"), PackedByteArray([4]), "and the position on the other channel") host.close() guest.close() func test_closing_a_guest_delivers_what_was_sent_before() -> void: var links := await _connected_pair() var host := links[0] var guest := links[1] var guest_peer: int = _first(host, "connected")["peer"] _events.clear() host.send(guest_peer, PackedByteArray([9, 9])) host.close_peer(guest_peer) await _pump([host, guest], 30) check_eq(_types(guest), ["message", "disconnected"], "the message comes before the end") check_eq(_types(host), ["disconnected"], "the host sees the guest go") check(not guest.is_open(), "the guest's connection is closed") host.close() func test_a_guest_that_quits_is_seen_leaving() -> void: var links := await _connected_pair() var host := links[0] var guest := links[1] _events.clear() guest.close() await _pump([host, guest], 10) check_eq(_types(host), ["disconnected"], "the host sees the guest go") host.close() ## Godot reports an error for every packet to a connection that is gone or ## being closed; the test runner turns each such error into a failure. func test_nothing_is_sent_to_connections_that_are_gone_or_closing() -> void: var host := NetLink.new() check_eq(host.host(0, "127.0.0.1"), OK, "the host listens") var first := NetLink.new() var second := NetLink.new() first.join("127.0.0.1", host.local_port()) second.join("127.0.0.1", host.local_port()) await _pump([host, first, second], 10) var peers: Array = _events[host].map(func(event: Dictionary) -> int: return event["peer"]) check_eq(peers.size(), 2, "two guests connected") var leaving: int = peers[0] var staying: int = peers[1] # Connections that never existed. host.send(12345, PackedByteArray([1])) host.send_figure(12345, PackedByteArray([1])) host.send_figure(-12345, PackedByteArray([1])) host.close_peer(12345) # A connection that is being closed, and everybody while it closes. host.close_peer(leaving) for frame in 10: host.send(leaving, PackedByteArray([2])) host.send_figure(leaving, PackedByteArray([2])) host.send_figure(-staying, PackedByteArray([2])) host.send_figure(0, PackedByteArray([2])) await _pump([host, first, second], 1) # The connection is gone now. host.send(leaving, PackedByteArray([3])) host.send_figure(-leaving, PackedByteArray([3])) await _pump([host, first, second], 5) check(_types(first).has("disconnected"), "the first guest was closed") check(not _types(second).has("disconnected"), "the second guest stays") for link in [host, first, second]: link.close() ## A packet goes to the connection it is addressed to, to every connection ## for peer 0, and to every connection but one for a negative peer. func test_packets_go_only_where_they_are_addressed() -> void: var host := NetLink.new() check_eq(host.host(0, "127.0.0.1"), OK, "the host listens") var guests: Array[NetLink] = [] var everyone: Array[NetLink] = [host] for i in 3: var guest := NetLink.new() guest.join("127.0.0.1", host.local_port()) guests.append(guest) everyone.append(guest) await _pump(everyone, 15) # Each guest says who it is, so that the host's id for it is known. for i in 3: guests[i].send(GwSession.HOST_PEER, PackedByteArray([i])) await _pump(everyone, 10) var peer_of: Array[int] = [0, 0, 0] for event: Dictionary in _events[host]: if event["type"] == "message": peer_of[event["bytes"][0]] = event["peer"] check(not peer_of.has(0), "the host knows all three guests") _events.clear() # Repeated, because a position that gets lost is no failure of the link. for repeat in 5: host.send(peer_of[0], PackedByteArray([10])) host.send_figure(peer_of[2], PackedByteArray([20])) host.send_figure(-peer_of[1], PackedByteArray([30])) host.send_figure(0, PackedByteArray([40])) await _pump(everyone, 2) await _pump(everyone, 10) check_eq(_received(guests[0], "message").size(), 5, "the first guest got the messages to it") check_eq(_received(guests[1], "message"), [], "the second guest got none") check_eq(_received(guests[2], "message"), [], "the third guest got none") var first := _received(guests[0], "figure") var second := _received(guests[1], "figure") var third := _received(guests[2], "figure") check(not first.has([20]), "the first guest got nothing for the third") check(first.has([30]) and first.has([40]), "the first guest got 'all but the second' and 'all'") check(not second.has([20]) and not second.has([30]), "the second guest got neither the one for the third nor 'all but itself'") check(second.has([40]), "the second guest got 'all'") check(third.has([20]) and third.has([30]) and third.has([40]), "the third guest got all three kinds") for link in everyone: link.close() func test_a_guest_gives_up_when_nobody_answers() -> void: # A port that was free a moment ago and has nobody listening now. var probe := NetLink.new() probe.host(0, "127.0.0.1") var port := probe.local_port() probe.close() var guest := NetLink.new() guest.connect_seconds = 0.2 check_eq(guest.join("127.0.0.1", port), OK, "connecting starts") await _pump([guest], 30) check_eq(_types(guest), ["failed"], "the guest gives up") check(not guest.is_open(), "and closes") func test_addresses_are_split_into_host_and_port() -> void: check_eq(NetLink.split_address("192.168.1.20"), ["192.168.1.20", NetLink.DEFAULT_PORT], "default port") check_eq(NetLink.split_address(" example.net:25000 "), ["example.net", 25000], "with port") for text in ["", ":25000", "host:", "host:0", "host:70000", "host:port", "a:1:2"]: check_eq(NetLink.split_address(text), [], "refused: '%s'" % text)