OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / project / test_e2e.gd

113 lines · gd ref:
extends SceneTree

# Two peers in one process: one holds a file, the other fetches it over a real
# UDX connection with real Noise encryption and real Protomux framing. This is
# the first test where bytes cross a socket rather than a loopback object.

func _init():
	var n := {"ok": 0, "bad": 0}
	var chk := func(c: bool, what: String):
		if c: n.ok += 1; print("  ok    ", what)
		else: n.bad += 1; print("  FAIL  ", what)

	print("HyperOverlay end-to-end over a live peer connection")

	var dir := "/tmp/claude-1000/ho_e2e"
	DirAccess.make_dir_recursive_absolute(dir + "/a")
	DirAccess.make_dir_recursive_absolute(dir + "/b")

	# The asset: 400 KB of deterministic noise, so it is several chunks.
	var src := dir + "/asset.bin"
	var data := PackedByteArray()
	data.resize(400 * 1024)
	var x := 12345
	for i in data.size():
		x = (x * 1103515245 + 12345) & 0x7fffffff
		data[i] = x >> 16 & 0xff
	var f := FileAccess.open(src, FileAccess.WRITE)
	f.store_buffer(data)
	f.close()
	var oid := FileAccess.get_sha256(src)   # placeholder; real hash comes from the overlay

	var server: Variant = ClassDB.instantiate("HyperswarmMultiplayerPeer")
	var client: Variant = ClassDB.instantiate("HyperswarmMultiplayerPeer")
	chk.call(server.create_server(38731) == OK, "server peer listening")
	chk.call(client.create_client("127.0.0.1", 38731) == OK, "client peer connecting")

	var ov_a: Variant = ClassDB.instantiate("HyperOverlay")   # holder
	var ov_b: Variant = ClassDB.instantiate("HyperOverlay")   # fetcher
	chk.call(ov_a.open(dir + "/a"), "holder overlay open")
	chk.call(ov_b.open(dir + "/b"), "fetcher overlay open")
	chk.call(ov_a.attach(server), "holder attached to the server peer")
	chk.call(ov_b.attach(client), "fetcher attached to the client peer")

	# ASSERT THE TRANSPORT FIRST. Without this, "no overlay channel" and "the two
	# peers never connected" look identical from the outside, and every fix aimed at
	# the overlay would be a guess. Establish the layer underneath, then build on it.
	var connected := {"server_saw": -1}
	server.peer_connected.connect(func(id): connected.server_saw = id)

	var t := 0
	while t < 15000:
		server.poll(); client.poll()
		if client.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED \
		   and connected.server_saw != -1:
			break
		OS.delay_msec(20); t += 20

	chk.call(client.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED,
			"the CLIENT peer reached CONNECTION_CONNECTED (status=%d)" % client.get_connection_status())
	chk.call(connected.server_saw != -1,
			"the SERVER peer saw a peer connect (id=%d)" % connected.server_saw)
	print("    [transport settled after %d ms]" % t)

	# Only now does asking about the overlay mean anything.
	var waited := 0
	while (ov_a.peer_count() == 0 or ov_b.peer_count() == 0) and waited < 15000:
		server.poll(); client.poll(); ov_a.poll(); ov_b.poll()
		OS.delay_msec(20); waited += 20
	chk.call(ov_a.peer_count() > 0, "an overlay channel appeared on the holder (%d)" % ov_a.peer_count())
	chk.call(ov_b.peer_count() > 0, "and on the fetcher (%d)" % ov_b.peer_count())

	# The overlay's own content hash: register, then read it back from the index by
	# fetching with the hash the holder computed. We get it from the holder's
	# describe() path instead — simplest is to compute it the same way the library
	# does, which GDScript cannot, so use a known-good round trip: the holder serves
	# under a caller-chosen id and the fetcher asks for that id.
	# (The overlay verifies content against THIS hash, so it must be the real one.)
	var real_oid: String = ov_a.content_hash_of(src)
	chk.call(real_oid.length() == 64, "the holder can hash a file the overlay's way (%s…)" % real_oid.substr(0, 12))
	ov_a.serve(real_oid, src)

	var got := {"path": "", "failed": ""}
	ov_b.content_fetched.connect(func(h, p): got.path = p)
	ov_b.content_failed.connect(func(h, r): got.failed = r)

	var dest := dir + "/b/fetched.bin"
	ov_b.fetch(real_oid, dest)

	waited = 0
	while got.path == "" and got.failed == "" and waited < 30000:
		server.poll(); client.poll(); ov_a.poll(); ov_b.poll()
		OS.delay_msec(20); waited += 20

	chk.call(got.failed == "", "the fetch did not fail (%s)" % got.failed)
	chk.call(got.path == dest, "it completed at the requested path")
	if FileAccess.file_exists(dest):
		var rf := FileAccess.open(dest, FileAccess.READ)
		var out := rf.get_buffer(rf.get_length())
		rf.close()
		chk.call(out.size() == data.size(), "%d bytes arrived" % out.size())
		chk.call(out == data, "and they are byte-identical to the original")
	else:
		chk.call(false, "the destination file exists")

	ov_b.close(); ov_a.close()
	ov_a.free(); ov_b.free()   # Nodes; the peers are RefCounted and free themselves

	chk.call(false, "(control: must appear as FAIL and be counted)")
	var real_bad: int = n.bad - 1
	print("\n  %d assertions pass, %d fail" % [n.ok, real_bad])
	print("PASS" if real_bad == 0 else "FAIL")
	quit(0 if real_bad == 0 else 1)