#!/usr/bin/env python3
"""
NimPacket Python Bindings - Complete Test Suite

Tests every function and constant exposed through the Python bindings.

Run from the python/ directory:
    cd python
    python examples/test_bindings.py
"""

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import nimpacket as np

passed = 0
failed = 0

def test(name, condition):
    global passed, failed
    if condition:
        passed += 1
    else:
        failed += 1
        print(f"  FAIL: {name}")


# =========================================================================
print("=== Version & Info ===")
# =========================================================================
test("version is string", isinstance(np.version(), str))
test("version is 0.2.0", np.version() == "0.2.0")
protos = np.supported_protocols()
test("supported_protocols is list", isinstance(protos, list))
test("has 10 protocols", len(protos) == 10)
test("has IPv4", "IPv4" in protos)
test("has DNS", "DNS" in protos)
test("has DHCP", "DHCP" in protos)
print(f"  {passed} passed")

# =========================================================================
print("=== Constants - TCP Flags ===")
# =========================================================================
test("TCP_FIN", np.TCP_FIN == 0x01)
test("TCP_SYN", np.TCP_SYN == 0x02)
test("TCP_RST", np.TCP_RST == 0x04)
test("TCP_PSH", np.TCP_PSH == 0x08)
test("TCP_ACK", np.TCP_ACK == 0x10)
test("TCP_URG", np.TCP_URG == 0x20)

# =========================================================================
print("=== Constants - IP Protocols ===")
# =========================================================================
test("IPPROTO_ICMP", np.IPPROTO_ICMP == 1)
test("IPPROTO_TCP", np.IPPROTO_TCP == 6)
test("IPPROTO_UDP", np.IPPROTO_UDP == 17)
test("IPPROTO_ICMPV6", np.IPPROTO_ICMPV6 == 58)
test("PROTO aliases", np.PROTO_TCP == np.IPPROTO_TCP)

# =========================================================================
print("=== Constants - ICMP Types ===")
# =========================================================================
test("ICMP_ECHO_REPLY", np.ICMP_ECHO_REPLY == 0)
test("ICMP_ECHO_REQUEST", np.ICMP_ECHO_REQUEST == 8)
test("ICMP_TIME_EXCEEDED", np.ICMP_TIME_EXCEEDED == 11)

# =========================================================================
print("=== Constants - EtherTypes ===")
# =========================================================================
test("ETHERTYPE_IPV4", np.ETHERTYPE_IPV4 == 0x0800)
test("ETHERTYPE_IPV6", np.ETHERTYPE_IPV6 == 0x86DD)
test("ETHERTYPE_ARP", np.ETHERTYPE_ARP == 0x0806)

# =========================================================================
print("=== Constants - ARP ===")
# =========================================================================
test("ARP_REQUEST", np.ARP_REQUEST == 1)
test("ARP_REPLY", np.ARP_REPLY == 2)

# =========================================================================
print("=== Constants - DNS Types ===")
# =========================================================================
test("DNS_TYPE_A", np.DNS_TYPE_A == 1)
test("DNS_TYPE_NS", np.DNS_TYPE_NS == 2)
test("DNS_TYPE_CNAME", np.DNS_TYPE_CNAME == 5)
test("DNS_TYPE_SOA", np.DNS_TYPE_SOA == 6)
test("DNS_TYPE_PTR", np.DNS_TYPE_PTR == 12)
test("DNS_TYPE_MX", np.DNS_TYPE_MX == 15)
test("DNS_TYPE_TXT", np.DNS_TYPE_TXT == 16)
test("DNS_TYPE_AAAA", np.DNS_TYPE_AAAA == 28)
test("DNS_TYPE_SRV", np.DNS_TYPE_SRV == 33)
test("DNS_TYPE_ANY", np.DNS_TYPE_ANY == 255)
test("DNS aliases", np.DNS_A == np.DNS_TYPE_A and np.DNS_MX == np.DNS_TYPE_MX)

# =========================================================================
print("=== Constants - DHCP Types ===")
# =========================================================================
test("DHCP_DISCOVER", np.DHCP_DISCOVER == 1)
test("DHCP_OFFER", np.DHCP_OFFER == 2)
test("DHCP_REQUEST", np.DHCP_REQUEST == 3)
test("DHCP_DECLINE", np.DHCP_DECLINE == 4)
test("DHCP_ACK", np.DHCP_ACK == 5)
test("DHCP_NAK", np.DHCP_NAK == 6)
test("DHCP_RELEASE", np.DHCP_RELEASE == 7)
test("DHCP_INFORM", np.DHCP_INFORM == 8)

# =========================================================================
print("=== Constants - ICMPv6 ===")
# =========================================================================
test("ICMPV6_ECHO_REQUEST", np.ICMPV6_ECHO_REQUEST == 128)
test("ICMPV6_ECHO_REPLY", np.ICMPV6_ECHO_REPLY == 129)
test("ICMPV6_ROUTER_SOLICITATION", np.ICMPV6_ROUTER_SOLICITATION == 133)
test("ICMPV6_NEIGHBOR_SOLICITATION", np.ICMPV6_NEIGHBOR_SOLICITATION == 135)

# =========================================================================
print("=== Constants - IPv6 Next Headers ===")
# =========================================================================
test("IPV6_NEXT_HEADER_TCP", np.IPV6_NEXT_HEADER_TCP == 6)
test("IPV6_NEXT_HEADER_UDP", np.IPV6_NEXT_HEADER_UDP == 17)
test("IPV6_NEXT_HEADER_ICMPV6", np.IPV6_NEXT_HEADER_ICMPV6 == 58)

# =========================================================================
print("=== Constants - Fragmentation ===")
# =========================================================================
test("FRAG_TINY", np.FRAG_TINY == 0)
test("FRAG_OVERLAP", np.FRAG_OVERLAP == 1)
test("FRAG_OUT_OF_ORDER", np.FRAG_OUT_OF_ORDER == 2)
test("FRAG_TIME_DELAYED", np.FRAG_TIME_DELAYED == 3)
test("FRAG_POLYMORPHIC", np.FRAG_POLYMORPHIC == 4)

# =========================================================================
print("=== Utilities - IP ===")
# =========================================================================
test("parse_ipv4", np.parse_ipv4("192.168.1.1") == 3232235777)
test("ip_to_string", np.ip_to_string(3232235777) == "192.168.1.1")
test("parse_ipv4 loopback", np.parse_ipv4("127.0.0.1") == 2130706433)
test("ip roundtrip", np.ip_to_string(np.parse_ipv4("10.0.0.1")) == "10.0.0.1")
test("ip_to_int alias", np.ip_to_int("1.2.3.4") == np.parse_ipv4("1.2.3.4"))
test("int_to_ip alias", np.int_to_ip(0x0A000001) == "10.0.0.1")

# =========================================================================
print("=== Utilities - MAC ===")
# =========================================================================
mac_bytes = np.parse_mac("AA:BB:CC:DD:EE:FF")
test("parse_mac type", isinstance(mac_bytes, bytes))
test("parse_mac length", len(mac_bytes) == 6)
test("parse_mac values", mac_bytes[0] == 0xAA and mac_bytes[5] == 0xFF)
test("mac_to_string", np.mac_to_string(mac_bytes) == "AA:BB:CC:DD:EE:FF")
test("mac roundtrip", np.mac_to_string(np.parse_mac("11:22:33:44:55:66")) == "11:22:33:44:55:66")
test("mac_to_bytes alias", np.mac_to_bytes("AB:CD:EF:01:23:45") == np.parse_mac("AB:CD:EF:01:23:45"))
bcast = np.broadcast_mac()
test("broadcast_mac", bcast == b'\xff\xff\xff\xff\xff\xff')

# =========================================================================
print("=== Utilities - IPv6 ===")
# =========================================================================
ipv6_bytes = np.parse_ipv6("2001:db8::1")
test("parse_ipv6 type", isinstance(ipv6_bytes, bytes))
test("parse_ipv6 length", len(ipv6_bytes) == 16)
test("ipv6 roundtrip", "2001:db8" in np.ipv6_to_string(ipv6_bytes).lower())

# =========================================================================
print("=== Utilities - Byte Order ===")
# =========================================================================
test("htons", np.htons(0x0050) == 0x5000 or np.htons(80) != 80)
test("ntohs roundtrip", np.ntohs(np.htons(1234)) == 1234)
test("htonl", np.htonl(1) != 1 or np.htonl(1) == 1)  # depends on endian
test("ntohl roundtrip", np.ntohl(np.htonl(0xDEADBEEF)) == 0xDEADBEEF)

# =========================================================================
print("=== IPv4 Header ===")
# =========================================================================
ip = np.create_ipv4_header("192.168.1.100", "10.0.0.1", np.IPPROTO_TCP)
test("ipv4 create type", isinstance(ip, bytes))
test("ipv4 create length", len(ip) == 20)
parsed = np.parse_ipv4_header(ip)
test("ipv4 parse type", isinstance(parsed, dict))
test("ipv4 version", parsed["version"] == 4)
test("ipv4 header_length", parsed["header_length"] == 5)
test("ipv4 protocol", parsed["protocol"] == np.IPPROTO_TCP)
test("ipv4 src_ip", parsed["src_ip"] == "192.168.1.100")
test("ipv4 dst_ip", parsed["dst_ip"] == "10.0.0.1")
test("ipv4 ttl", parsed["ttl"] == 64)
cs = np.calculate_ipv4_checksum(ip)
test("ipv4 checksum", isinstance(cs, int))

# =========================================================================
print("=== IPv6 Header ===")
# =========================================================================
ipv6 = np.create_ipv6_header("2001:db8::1", "2001:db8::2", np.IPV6_NEXT_HEADER_TCP, payload_length=20, hop_limit=128)
test("ipv6 create type", isinstance(ipv6, bytes))
test("ipv6 create length", len(ipv6) == 40)
parsed = np.parse_ipv6_header(ipv6)
test("ipv6 parse type", isinstance(parsed, dict))
test("ipv6 version", parsed["version"] == 6)
test("ipv6 next_header", parsed["next_header"] == np.IPV6_NEXT_HEADER_TCP)
test("ipv6 hop_limit", parsed["hop_limit"] == 128)
test("ipv6 payload_length", parsed["payload_length"] == 20)

# =========================================================================
print("=== TCP Header ===")
# =========================================================================
tcp = np.create_tcp_header(54321, 80, np.TCP_SYN, seq_num=1000, ack_num=0, window_size=65535)
test("tcp create type", isinstance(tcp, bytes))
test("tcp create length", len(tcp) == 20)
parsed = np.parse_tcp_header(tcp)
test("tcp parse type", isinstance(parsed, dict))
test("tcp src_port", parsed["src_port"] == 54321)
test("tcp dst_port", parsed["dst_port"] == 80)
test("tcp flags SYN", parsed["flags"] == np.TCP_SYN)
test("tcp seq_num", parsed["seq_num"] == 1000)
test("tcp window_size", parsed["window_size"] == 65535)
cs = np.calculate_tcp_checksum(ip, tcp)
test("tcp checksum", isinstance(cs, int))

# =========================================================================
print("=== UDP Header ===")
# =========================================================================
udp = np.create_udp_header(12345, 53, length=16)
test("udp create type", isinstance(udp, bytes))
test("udp create length", len(udp) == 8)
parsed = np.parse_udp_header(udp)
test("udp parse type", isinstance(parsed, dict))
test("udp src_port", parsed["src_port"] == 12345)
test("udp dst_port", parsed["dst_port"] == 53)
test("udp length", parsed["length"] == 16)
cs = np.calculate_udp_checksum(ip, udp)
test("udp checksum", isinstance(cs, int))

# =========================================================================
print("=== ICMP Header ===")
# =========================================================================
icmp = np.create_icmp_header(np.ICMP_ECHO_REQUEST, code=0, identifier=1234, sequence=5)
test("icmp create type", isinstance(icmp, bytes))
test("icmp create length", len(icmp) == 8)
parsed = np.parse_icmp_header(icmp)
test("icmp parse type", isinstance(parsed, dict))
test("icmp type", parsed["icmp_type"] == np.ICMP_ECHO_REQUEST)
test("icmp code", parsed["code"] == 0)
test("icmp identifier", parsed["identifier"] == 1234)
test("icmp sequence", parsed["sequence"] == 5)
cs = np.calculate_icmp_checksum(icmp)
test("icmp checksum", isinstance(cs, int))

# =========================================================================
print("=== ICMPv6 Header ===")
# =========================================================================
icmpv6_req = np.create_icmpv6_echo_request(1000, 1)
test("icmpv6 echo_request type", isinstance(icmpv6_req, bytes))
test("icmpv6 echo_request length", len(icmpv6_req) == 8)
parsed = np.parse_icmpv6_header(icmpv6_req)
test("icmpv6 parse type", isinstance(parsed, dict))
test("icmpv6 type", parsed["icmp_type"] == np.ICMPV6_ECHO_REQUEST)

icmpv6_rep = np.create_icmpv6_echo_reply(1000, 1)
test("icmpv6 echo_reply type", isinstance(icmpv6_rep, bytes))
parsed2 = np.parse_icmpv6_header(icmpv6_rep)
test("icmpv6 reply type", parsed2["icmp_type"] == np.ICMPV6_ECHO_REPLY)

# =========================================================================
print("=== Ethernet Header ===")
# =========================================================================
eth = np.create_ethernet_header("AA:BB:CC:DD:EE:FF", "11:22:33:44:55:66", np.ETHERTYPE_IPV4)
test("eth create type", isinstance(eth, bytes))
test("eth create length", len(eth) == 14)
parsed = np.parse_ethernet_header(eth)
test("eth parse type", isinstance(parsed, dict))
test("eth src_mac", parsed["src_mac"] == "AA:BB:CC:DD:EE:FF")
test("eth dst_mac", parsed["dst_mac"] == "11:22:33:44:55:66")
test("eth ether_type", parsed["ether_type"] == np.ETHERTYPE_IPV4)

# =========================================================================
print("=== ARP ===")
# =========================================================================
arp_req = np.create_arp_request("AA:BB:CC:DD:EE:FF", "192.168.1.100", "192.168.1.1")
test("arp request type", isinstance(arp_req, bytes))
test("arp request length", len(arp_req) == 28)
parsed = np.parse_arp_packet(arp_req)
test("arp parse type", isinstance(parsed, dict))
test("arp opcode request", parsed["opcode"] == np.ARP_REQUEST)
test("arp sender_mac", parsed["sender_mac"] == "AA:BB:CC:DD:EE:FF")
test("arp sender_ip", parsed["sender_ip"] == "192.168.1.100")
test("arp target_ip", parsed["target_ip"] == "192.168.1.1")

arp_rep = np.create_arp_reply("AA:BB:CC:DD:EE:FF", "192.168.1.100", "11:22:33:44:55:66", "192.168.1.1")
test("arp reply type", isinstance(arp_rep, bytes))
parsed2 = np.parse_arp_packet(arp_rep)
test("arp opcode reply", parsed2["opcode"] == np.ARP_REPLY)
test("arp reply target_mac", parsed2["target_mac"] == "11:22:33:44:55:66")

# =========================================================================
print("=== DNS ===")
# =========================================================================
# Encode domain name
enc = np.encode_domain_name("www.example.com")
test("dns encode type", isinstance(enc, bytes))
test("dns encode first label", enc[0] == 3)  # "www" = 3 chars
test("dns encode null term", enc[-1] == 0)

# Create query for each type
for name, qtype in [("A", np.DNS_TYPE_A), ("AAAA", np.DNS_TYPE_AAAA),
                     ("MX", np.DNS_TYPE_MX), ("TXT", np.DNS_TYPE_TXT),
                     ("CNAME", np.DNS_TYPE_CNAME), ("NS", np.DNS_TYPE_NS),
                     ("PTR", np.DNS_TYPE_PTR), ("SOA", np.DNS_TYPE_SOA),
                     ("SRV", np.DNS_TYPE_SRV), ("ANY", np.DNS_TYPE_ANY)]:
    q = np.create_dns_query(f"test.example.com", qtype, 0x1234)
    test(f"dns query {name} create", isinstance(q, bytes) and len(q) > 12)

# Parse roundtrip
query = np.create_dns_query("google.com", np.DNS_TYPE_A, 0xABCD)
parsed = np.parse_dns_packet(query)
test("dns parse type", isinstance(parsed, dict))
test("dns transaction_id", parsed["transaction_id"] == 0xABCD)
test("dns is_response", parsed["is_response"] == False)
test("dns recursion_desired", parsed["recursion_desired"] == True)
test("dns question_count", parsed["question_count"] == 1)
test("dns question name", parsed["questions"][0]["name"] == "google.com")
test("dns question type", parsed["questions"][0]["type"] == np.DNS_TYPE_A)
test("dns question type_name", parsed["questions"][0]["type_name"] == "A")

# =========================================================================
print("=== DHCP ===")
# =========================================================================
# DISCOVER
disc = np.create_dhcp_discover("AA:BB:CC:DD:EE:FF", 0x12345678)
test("dhcp discover type", isinstance(disc, bytes))
test("dhcp discover min 300", len(disc) >= 300)
parsed = np.parse_dhcp_packet(disc)
test("dhcp discover parse", isinstance(parsed, dict))
test("dhcp discover op", parsed["op"] == 1)
test("dhcp discover htype", parsed["htype"] == 1)
test("dhcp discover hlen", parsed["hlen"] == 6)
test("dhcp discover xid", parsed["transaction_id"] == 0x12345678)
test("dhcp discover mac", parsed["client_mac"] == "AA:BB:CC:DD:EE:FF")
test("dhcp discover msg_type", parsed["message_type"] == "DISCOVER")
test("dhcp discover msg_code", parsed["message_type_code"] == np.DHCP_DISCOVER)
test("dhcp discover flags", parsed["flags"] == 0x8000)  # broadcast
test("dhcp discover options", len(parsed["options"]) >= 1)

# REQUEST
req = np.create_dhcp_request("11:22:33:44:55:66", "192.168.1.50", "192.168.1.1", 0xDEADBEEF)
parsed = np.parse_dhcp_packet(req)
test("dhcp request msg_type", parsed["message_type"] == "REQUEST")
test("dhcp request xid", parsed["transaction_id"] == 0xDEADBEEF)
test("dhcp request mac", parsed["client_mac"] == "11:22:33:44:55:66")
test("dhcp request has options", len(parsed["options"]) >= 3)  # msg_type + requested_ip + server_id

# RELEASE
rel = np.create_dhcp_release("AA:BB:CC:DD:EE:FF", "10.0.0.100", "10.0.0.1", 0xCAFEBABE)
parsed = np.parse_dhcp_packet(rel)
test("dhcp release msg_type", parsed["message_type"] == "RELEASE")
test("dhcp release xid", parsed["transaction_id"] == 0xCAFEBABE)
test("dhcp release client_ip", parsed["client_ip"] == "10.0.0.100")

# Auto transaction ID
disc_auto = np.create_dhcp_discover("AA:BB:CC:DD:EE:FF")
parsed_auto = np.parse_dhcp_packet(disc_auto)
test("dhcp auto xid", parsed_auto["transaction_id"] != 0)

# =========================================================================
print("=== Packet Builders ===")
# =========================================================================
# TCP SYN
syn = np.build_tcp_syn_packet("192.168.1.100", "10.0.0.1", 54321, 80, ttl=128)
test("build tcp syn type", isinstance(syn, bytes))
test("build tcp syn length", len(syn) == 40)
parsed = np.parse_packet(syn)
test("build tcp syn src_ip", parsed["ipv4"]["src_ip"] == "192.168.1.100")
test("build tcp syn dst_ip", parsed["ipv4"]["dst_ip"] == "10.0.0.1")
test("build tcp syn src_port", parsed["tcp"]["src_port"] == 54321)
test("build tcp syn dst_port", parsed["tcp"]["dst_port"] == 80)
test("build tcp syn ttl", parsed["ipv4"]["ttl"] == 128)
test("build tcp syn flags", parsed["tcp"]["flags"] == np.TCP_SYN)

# ICMP Echo
ping = np.build_icmp_echo_packet("10.0.0.1", "8.8.8.8", 9999, 42, ttl=64)
test("build icmp type", isinstance(ping, bytes))
test("build icmp length", len(ping) == 28)
parsed = np.parse_packet(ping)
test("build icmp src", parsed["ipv4"]["src_ip"] == "10.0.0.1")
test("build icmp dst", parsed["ipv4"]["dst_ip"] == "8.8.8.8")
test("build icmp id", parsed["icmp"]["identifier"] == 9999)
test("build icmp seq", parsed["icmp"]["sequence"] == 42)
test("build icmp type field", parsed["icmp"]["type"] == np.ICMP_ECHO_REQUEST)

# ICMP with payload
ping_payload = np.build_icmp_echo_packet("1.2.3.4", "5.6.7.8", 100, 1, payload=b"ABCD")
parsed = np.parse_packet(ping_payload)
test("build icmp with payload", parsed["payload_length"] == 4)

# UDP
udp_pkt = np.build_udp_packet("10.0.0.1", "10.0.0.2", 5000, 53, payload=b"hello")
test("build udp type", isinstance(udp_pkt, bytes))
parsed = np.parse_packet(udp_pkt)
test("build udp src", parsed["ipv4"]["src_ip"] == "10.0.0.1")
test("build udp dst_port", parsed["udp"]["dst_port"] == 53)
test("build udp payload", parsed["payload_length"] == 5)

# ARP Frame (Ethernet + ARP)
arp_frame = np.build_arp_frame("AA:BB:CC:DD:EE:FF", "192.168.1.100", "192.168.1.1")
test("build arp frame type", isinstance(arp_frame, bytes))
test("build arp frame length", len(arp_frame) == 42)  # 14 eth + 28 arp
parsed = np.parse_packet(arp_frame)
test("build arp has_ethernet", parsed["has_ethernet"] == True)
test("build arp has_arp", parsed["has_arp"] == True)
test("build arp sender_ip", parsed["arp"]["sender_ip"] == "192.168.1.100")

# ARP Reply Frame
arp_reply_frame = np.build_arp_frame("AA:BB:CC:DD:EE:FF", "192.168.1.100", "192.168.1.1", dst_mac="11:22:33:44:55:66")
parsed = np.parse_packet(arp_reply_frame)
test("build arp reply", parsed["arp"]["opcode"] == np.ARP_REPLY)

# =========================================================================
print("=== Full Packet Parsing ===")
# =========================================================================
# IPv4 + TCP
syn = np.build_tcp_syn_packet("1.2.3.4", "5.6.7.8", 1111, 2222)
p = np.parse_packet(syn)
test("parse_packet has ipv4", "ipv4" in p)
test("parse_packet has tcp", "tcp" in p)
test("parse_packet no ipv6", p["has_ipv6"] == False)

# IPv4 + UDP
udp_p = np.build_udp_packet("1.1.1.1", "2.2.2.2", 3333, 4444, b"data")
p = np.parse_packet(udp_p)
test("parse_packet udp", "udp" in p)

# IPv4 + ICMP
icmp_p = np.build_icmp_echo_packet("3.3.3.3", "4.4.4.4", 1, 1)
p = np.parse_packet(icmp_p)
test("parse_packet icmp", "icmp" in p)

# Ethernet + ARP
arp_f = np.build_arp_frame("AA:BB:CC:DD:EE:FF", "10.0.0.1", "10.0.0.2")
p = np.parse_packet(arp_f)
test("parse_packet ethernet", p["has_ethernet"] == True)
test("parse_packet arp", p["has_arp"] == True)

# =========================================================================
print("=== IP Fragmentation ===")
# =========================================================================
ip_hdr = np.create_ipv4_header("1.2.3.4", "5.6.7.8", np.IPPROTO_TCP, total_length=60)
payload = bytes(range(40))

frags = np.fragment_packet(ip_hdr, payload, np.FRAG_TINY, fragment_size=8)
test("frag tiny type", isinstance(frags, list))
test("frag tiny count", len(frags) > 1)
test("frag tiny each is bytes", all(isinstance(f, bytes) for f in frags))

frags2 = np.fragment_packet(ip_hdr, payload, np.FRAG_OUT_OF_ORDER, fragment_size=16)
test("frag out_of_order", len(frags2) > 1)

frags3 = np.fragment_packet(ip_hdr, payload, np.FRAG_OVERLAP)
test("frag overlap", len(frags3) >= 1)

frags4 = np.fragment_packet(ip_hdr, payload, np.FRAG_TIME_DELAYED)
test("frag time_delayed", len(frags4) >= 1)

frags5 = np.fragment_packet(ip_hdr, payload, np.FRAG_POLYMORPHIC)
test("frag polymorphic", len(frags5) >= 1)

# =========================================================================
print("=== Raw Socket (non-privileged checks) ===")
# =========================================================================
test("is_admin type", isinstance(np.is_admin(), bool))

# =========================================================================
print("=== Aliases ===")
# =========================================================================
test("IP alias", np.IP == np.create_ipv4_header)
test("IPv4 alias", np.IPv4 == np.create_ipv4_header)
test("IPv6 alias", np.IPv6 == np.create_ipv6_header)
test("TCP alias", np.TCP == np.create_tcp_header)
test("UDP alias", np.UDP == np.create_udp_header)
test("ICMP alias", np.ICMP == np.create_icmp_header)
test("Ethernet alias", np.Ethernet == np.create_ethernet_header)
test("ARP alias", np.ARP == np.create_arp_request)
test("DNS alias", np.DNS == np.create_dns_query)
test("DHCP alias", np.DHCP == np.create_dhcp_discover)
test("help callable", callable(np.help))

# =========================================================================
# RESULTS
# =========================================================================
print()
print("=" * 60)
total = passed + failed
if failed == 0:
    print(f"ALL {total} TESTS PASSED")
else:
    print(f"{passed}/{total} passed, {failed} FAILED")
print("=" * 60)

sys.exit(0 if failed == 0 else 1)
