test_core_net.odin 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. /*
  2. Copyright 2021 Jeroen van Rijn <[email protected]>.
  3. Made available under Odin's BSD-3 license.
  4. List of contributors:
  5. Jeroen van Rijn: Initial implementation.
  6. graphitemaster: pton/ntop IANA test vectors
  7. A test suite for `core:net`
  8. */
  9. package test_core_net
  10. import "core:testing"
  11. import "core:mem"
  12. import "core:fmt"
  13. import "core:net"
  14. import "core:strconv"
  15. import "core:sync"
  16. import "core:time"
  17. import "core:thread"
  18. import "core:os"
  19. _, _ :: time, thread
  20. TEST_count := 0
  21. TEST_fail := 0
  22. t := &testing.T{}
  23. when ODIN_TEST {
  24. expect :: testing.expect
  25. log :: testing.log
  26. } else {
  27. expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
  28. TEST_count += 1
  29. if !condition {
  30. TEST_fail += 1
  31. fmt.printf("[%v] %v\n", loc, message)
  32. return
  33. }
  34. }
  35. log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
  36. fmt.printf("[%v] ", loc)
  37. fmt.printf("log: %v\n", v)
  38. }
  39. }
  40. _tracking_allocator := mem.Tracking_Allocator{}
  41. print_tracking_allocator_report :: proc() {
  42. for _, leak in _tracking_allocator.allocation_map {
  43. fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
  44. }
  45. for bf in _tracking_allocator.bad_free_array {
  46. fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory)
  47. }
  48. }
  49. main :: proc() {
  50. mem.tracking_allocator_init(&_tracking_allocator, context.allocator)
  51. context.allocator = mem.tracking_allocator(&_tracking_allocator)
  52. address_parsing_test(t)
  53. tcp_tests(t)
  54. split_url_test(t)
  55. join_url_test(t)
  56. fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
  57. print_tracking_allocator_report()
  58. if TEST_fail > 0 {
  59. os.exit(1)
  60. }
  61. }
  62. @test
  63. address_parsing_test :: proc(t: ^testing.T) {
  64. for vector in IP_Address_Parsing_Test_Vectors {
  65. kind := ""
  66. switch vector.family {
  67. case .IP4: kind = "[IPv4]"
  68. case .IP4_Alt: kind = "[IPv4 Non-Decimal]"
  69. case .IP6: kind = "[IPv6]"
  70. case: panic("Add support to the test for this type.")
  71. }
  72. valid := len(vector.binstr) > 0
  73. fmt.printf("%v %v\n", kind, vector.input)
  74. msg := "-set a proper message-"
  75. switch vector.family {
  76. case .IP4, .IP4_Alt:
  77. /*
  78. Does `net.parse_ip4_address` think we parsed the address properly?
  79. */
  80. non_decimal := vector.family == .IP4_Alt
  81. any_addr := net.parse_address(vector.input, non_decimal)
  82. parsed_ok := any_addr != nil
  83. parsed: net.IP4_Address
  84. /*
  85. Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake.
  86. */
  87. switch addr in any_addr {
  88. case net.IP4_Address:
  89. parsed = addr
  90. case net.IP6_Address:
  91. parsed_ok = false
  92. msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr)
  93. expect(t, false, msg)
  94. }
  95. if !parsed_ok && valid {
  96. msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr))
  97. } else if parsed_ok && !valid {
  98. msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed)
  99. }
  100. expect(t, parsed_ok == valid, msg)
  101. if valid && parsed_ok {
  102. actual_binary := address_to_binstr(parsed)
  103. msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
  104. expect(t, actual_binary == vector.binstr, msg)
  105. /*
  106. Do we turn an address back into the same string properly?
  107. No point in testing the roundtrip if the first part failed.
  108. */
  109. if len(vector.output) > 0 && actual_binary == vector.binstr {
  110. stringified := net.address_to_string(parsed)
  111. msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
  112. expect(t, stringified == vector.output, msg)
  113. }
  114. }
  115. case .IP6:
  116. /*
  117. Do we parse the address properly?
  118. */
  119. parsed, parsed_ok := net.parse_ip6_address(vector.input)
  120. if !parsed_ok && valid {
  121. msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr))
  122. } else if parsed_ok && !valid {
  123. msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed)
  124. }
  125. expect(t, parsed_ok == valid, msg)
  126. if valid && parsed_ok {
  127. actual_binary := address_to_binstr(parsed)
  128. msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
  129. expect(t, actual_binary == vector.binstr, msg)
  130. /*
  131. Do we turn an address back into the same string properly?
  132. No point in testing the roundtrip if the first part failed.
  133. */
  134. if len(vector.output) > 0 && actual_binary == vector.binstr {
  135. stringified := net.address_to_string(parsed)
  136. msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
  137. expect(t, stringified == vector.output, msg)
  138. }
  139. }
  140. }
  141. }
  142. }
  143. address_to_binstr :: proc(address: net.Address) -> (binstr: string) {
  144. switch t in address {
  145. case net.IP4_Address:
  146. b := transmute(u32be)t
  147. return fmt.tprintf("%08x", b)
  148. case net.IP6_Address:
  149. b := transmute(u128be)t
  150. return fmt.tprintf("%32x", b)
  151. case:
  152. return ""
  153. }
  154. unreachable()
  155. }
  156. binstr_to_address :: proc(binstr: string) -> (address: net.Address) {
  157. switch len(binstr) {
  158. case 8: // IPv4
  159. a, ok := strconv.parse_u64_of_base(binstr, 16)
  160. expect(t, ok, "failed to parse test case bin string")
  161. ipv4 := u32be(a)
  162. return net.IP4_Address(transmute([4]u8)ipv4)
  163. case 32: // IPv6
  164. a, ok := strconv.parse_u128_of_base(binstr, 16)
  165. expect(t, ok, "failed to parse test case bin string")
  166. ipv4 := u128be(a)
  167. return net.IP6_Address(transmute([8]u16be)ipv4)
  168. case 0:
  169. return nil
  170. }
  171. panic("Invalid test case")
  172. }
  173. Kind :: enum {
  174. IP4, // Decimal IPv4
  175. IP4_Alt, // Non-decimal address
  176. IP6, // Hex IPv6 or mixed IPv4/IPv6.
  177. }
  178. IP_Address_Parsing_Test_Vector :: struct {
  179. // Give it to the IPv4 or IPv6 parser?
  180. family: Kind,
  181. // Input address to try and parse.
  182. input: string,
  183. /*
  184. Hexadecimal representation of the expected numeric value of the address.
  185. Zero length means input is invalid and the parser should report failure.
  186. */
  187. binstr: string,
  188. // Expected `address_to_string` output, if a valid input and this string is non-empty.
  189. output: string,
  190. }
  191. IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{
  192. // dotted-decimal notation
  193. { .IP4, "0.0.0.0", "00000000", "0.0.0.0" },
  194. { .IP4, "127.0.0.1", "7f000001", "127.0.0.1" },
  195. { .IP4, "10.0.128.31", "0a00801f", "10.0.128.31" },
  196. { .IP4, "255.255.255.255", "ffffffff", "255.255.255.255"},
  197. // Odin custom: Address + port, valid
  198. { .IP4, "0.0.0.0:80", "00000000", "0.0.0.0" },
  199. { .IP4, "127.0.0.1:80", "7f000001", "127.0.0.1" },
  200. { .IP4, "10.0.128.31:80", "0a00801f", "10.0.128.31" },
  201. { .IP4, "255.255.255.255:80", "ffffffff", "255.255.255.255"},
  202. { .IP4, "[0.0.0.0]:80", "00000000", "0.0.0.0" },
  203. { .IP4, "[127.0.0.1]:80", "7f000001", "127.0.0.1" },
  204. { .IP4, "[10.0.128.31]:80", "0a00801f", "10.0.128.31" },
  205. { .IP4, "[255.255.255.255]:80", "ffffffff", "255.255.255.255"},
  206. // Odin custom: Address + port, invalid
  207. { .IP4, "[]:80", "", ""},
  208. { .IP4, "[0.0.0.0]", "", ""},
  209. { .IP4, "[127.0.0.1]:", "", ""},
  210. { .IP4, "[10.0.128.31] :80", "", ""},
  211. { .IP4, "[255.255.255.255]:65536", "", ""},
  212. // numbers-and-dots notation, but not dotted-decimal
  213. { .IP4_Alt, "1.2.03.4", "01020304", ""},
  214. { .IP4_Alt, "1.2.0x33.4", "01023304", ""},
  215. { .IP4_Alt, "1.2.0XAB.4", "0102ab04", ""},
  216. { .IP4_Alt, "1.2.0xabcd", "0102abcd", ""},
  217. { .IP4_Alt, "1.0xabcdef", "01abcdef", ""},
  218. { .IP4_Alt, "0x01abcdef", "01abcdef", ""},
  219. { .IP4_Alt, "00377.0x0ff.65534", "fffffffe", ""},
  220. // invalid as decimal address
  221. { .IP4, "", "", ""},
  222. { .IP4, ".1.2.3", "", ""},
  223. { .IP4, "1..2.3", "", ""},
  224. { .IP4, "1.2.3.", "", ""},
  225. { .IP4, "1.2.3.4.5", "", ""},
  226. { .IP4, "1.2.3.a", "", ""},
  227. { .IP4, "1.256.2.3", "", ""},
  228. { .IP4, "1.2.4294967296.3", "", ""},
  229. { .IP4, "1.2.-4294967295.3", "", ""},
  230. { .IP4, "1.2. 3.4", "", ""},
  231. // invalid as non-decimal address
  232. { .IP4_Alt, "", "", ""},
  233. { .IP4_Alt, ".1.2.3", "", ""},
  234. { .IP4_Alt, "1..2.3", "", ""},
  235. { .IP4_Alt, "1.2.3.", "", ""},
  236. { .IP4_Alt, "1.2.3.4.5", "", ""},
  237. { .IP4_Alt, "1.2.3.a", "", ""},
  238. { .IP4_Alt, "1.256.2.3", "", ""},
  239. { .IP4_Alt, "1.2.4294967296.3", "", ""},
  240. { .IP4_Alt, "1.2.-4294967295.3", "", ""},
  241. { .IP4_Alt, "1.2. 3.4", "", ""},
  242. // Valid IPv6 addresses
  243. { .IP6, "::", "00000000000000000000000000000000", "::"},
  244. { .IP6, "::1", "00000000000000000000000000000001", "::1"},
  245. { .IP6, "::192.168.1.1", "000000000000000000000000c0a80101", "::c0a8:101"},
  246. { .IP6, "0000:0000:0000:0000:0000:ffff:255.255.255.255", "00000000000000000000ffffffffffff", "::ffff:ffff:ffff"},
  247. { .IP6, "0:0:0:0:0:0:192.168.1.1", "000000000000000000000000c0a80101", "::c0a8:101"},
  248. { .IP6, "0:0::0:0:0:192.168.1.1", "000000000000000000000000c0a80101", "::c0a8:101"},
  249. { .IP6, "::ffff:192.168.1.1", "00000000000000000000ffffc0a80101", "::ffff:c0a8:101"},
  250. { .IP6, "a:0b:00c:000d:E:F::", "000a000b000c000d000e000f00000000", "a:b:c:d:e:f::"},
  251. { .IP6, "1:2:3:4:5:6::", "00010002000300040005000600000000", "1:2:3:4:5:6::"},
  252. { .IP6, "1:2:3:4:5:6:7::", "00010002000300040005000600070000", "1:2:3:4:5:6:7:0"},
  253. { .IP6, "::1:2:3:4:5:6", "00000000000100020003000400050006", "::1:2:3:4:5:6"},
  254. { .IP6, "::1:2:3:4:5:6:7", "00000001000200030004000500060007", "0:1:2:3:4:5:6:7"},
  255. { .IP6, "a:b::c:d:e:f", "000a000b00000000000c000d000e000f", "a:b::c:d:e:f"},
  256. { .IP6, "0:0:0:0:0:ffff:c0a8:5e4", "00000000000000000000ffffc0a805e4", "::ffff:c0a8:5e4"},
  257. { .IP6, "0::ffff:c0a8:5e4", "00000000000000000000ffffc0a805e4", "::ffff:c0a8:5e4"},
  258. // If multiple zero runs are present, shorten the longest one.
  259. { .IP6, "1:0:0:2:0:0:0:3", "00010000000000020000000000000003", "1:0:0:2::3"},
  260. // Invalid IPv6 addresses
  261. { .IP6, "", "", ""},
  262. { .IP6, ":", "", ""},
  263. { .IP6, ":::", "", ""},
  264. { .IP6, "192.168.1.1", "", ""},
  265. { .IP6, ":192.168.1.1", "", ""},
  266. { .IP6, "::012.34.56.78", "", ""},
  267. { .IP6, ":ffff:192.168.1.1", "", ""},
  268. { .IP6, ".192.168.1.1", "", ""},
  269. { .IP6, ":.192.168.1.1", "", ""},
  270. { .IP6, "a:0b:00c:000d:0000e:f::", "", ""},
  271. { .IP6, "1:2:3:4:5:6:7:8::", "", ""},
  272. { .IP6, "1:2:3:4:5:6:7::9", "", ""},
  273. { .IP6, "::1:2:3:4:5:6:7:8", "", ""},
  274. { .IP6, "ffff:c0a8:5e4", "", ""},
  275. { .IP6, ":ffff:c0a8:5e4", "", ""},
  276. { .IP6, "0:0:0:0:ffff:c0a8:5e4", "", ""},
  277. { .IP6, "::0::ffff:c0a8:5e4", "", ""},
  278. { .IP6, "c0a8", "", ""},
  279. }
  280. tcp_tests :: proc(t: ^testing.T) {
  281. fmt.println("Testing two servers trying to bind to the same endpoint...")
  282. two_servers_binding_same_endpoint(t)
  283. fmt.println("Testing client connecting to a closed port...")
  284. client_connects_to_closed_port(t)
  285. fmt.println("Testing client sending server data...")
  286. client_sends_server_data(t)
  287. }
  288. ENDPOINT := net.Endpoint{
  289. net.IP4_Address{127, 0, 0, 1},
  290. 9999,
  291. }
  292. @(test)
  293. two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
  294. skt1, err1 := net.listen_tcp(ENDPOINT)
  295. defer net.close(skt1)
  296. skt2, err2 := net.listen_tcp(ENDPOINT)
  297. defer net.close(skt2)
  298. expect(t, err1 == nil, "expected first server binding to endpoint to do so without error")
  299. expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
  300. }
  301. @(test)
  302. client_connects_to_closed_port :: proc(t: ^testing.T) {
  303. skt, err := net.dial_tcp(ENDPOINT)
  304. defer net.close(skt)
  305. expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
  306. }
  307. @(test)
  308. client_sends_server_data :: proc(t: ^testing.T) {
  309. CONTENT: string: "Hellope!"
  310. SEND_TIMEOUT :: time.Duration(1 * time.Second)
  311. RECV_TIMEOUT :: time.Duration(1 * time.Second)
  312. Thread_Data :: struct {
  313. t: ^testing.T,
  314. skt: net.Any_Socket,
  315. err: net.Network_Error,
  316. tid: ^thread.Thread,
  317. data: [1024]u8, // Received data and its length
  318. length: int,
  319. wg: ^sync.Wait_Group,
  320. }
  321. tcp_client :: proc(thread_data: rawptr) {
  322. r := transmute(^Thread_Data)thread_data
  323. defer sync.wait_group_done(r.wg)
  324. if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil {
  325. log(r.t, r.err)
  326. return
  327. }
  328. net.set_option(r.skt, .Send_Timeout, SEND_TIMEOUT)
  329. _, r.err = net.send(r.skt, transmute([]byte)CONTENT)
  330. }
  331. tcp_server :: proc(thread_data: rawptr) {
  332. r := transmute(^Thread_Data)thread_data
  333. defer sync.wait_group_done(r.wg)
  334. log(r.t, "tcp_server listen")
  335. if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
  336. sync.wait_group_done(r.wg)
  337. log(r.t, r.err)
  338. return
  339. }
  340. sync.wait_group_done(r.wg)
  341. log(r.t, "tcp_server accept")
  342. client: net.TCP_Socket
  343. if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
  344. log(r.t, r.err)
  345. return
  346. }
  347. defer net.close(client)
  348. net.set_option(client, .Receive_Timeout, RECV_TIMEOUT)
  349. r.length, r.err = net.recv_tcp(client, r.data[:])
  350. return
  351. }
  352. thread_data := [2]Thread_Data{}
  353. wg: sync.Wait_Group
  354. sync.wait_group_add(&wg, 1)
  355. thread_data[0].t = t
  356. thread_data[0].wg = &wg
  357. thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
  358. log(t, "waiting for server to start listening")
  359. sync.wait_group_wait(&wg)
  360. log(t, "starting up client")
  361. sync.wait_group_add(&wg, 2)
  362. thread_data[1].t = t
  363. thread_data[1].wg = &wg
  364. thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
  365. defer {
  366. net.close(thread_data[0].skt)
  367. thread.destroy(thread_data[0].tid)
  368. net.close(thread_data[1].skt)
  369. thread.destroy(thread_data[1].tid)
  370. }
  371. log(t, "waiting for threads to finish")
  372. sync.wait_group_wait(&wg)
  373. log(t, "threads finished")
  374. okay := thread_data[0].err == nil && thread_data[1].err == nil
  375. msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)
  376. expect(t, okay, msg)
  377. received := string(thread_data[0].data[:thread_data[0].length])
  378. okay = received == CONTENT
  379. msg = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received)
  380. expect(t, okay, msg)
  381. }
  382. URL_Test :: struct {
  383. scheme, host, path: string,
  384. queries: map[string]string,
  385. url: []string,
  386. }
  387. @test
  388. split_url_test :: proc(t: ^testing.T) {
  389. test_cases := []URL_Test{
  390. {
  391. "http", "example.com", "/",
  392. {},
  393. {"http://example.com"},
  394. },
  395. {
  396. "https", "odin-lang.org", "/",
  397. {},
  398. {"https://odin-lang.org"},
  399. },
  400. {
  401. "https", "odin-lang.org", "/docs/",
  402. {},
  403. {"https://odin-lang.org/docs/"},
  404. },
  405. {
  406. "https", "odin-lang.org", "/docs/overview",
  407. {},
  408. {"https://odin-lang.org/docs/overview"},
  409. },
  410. {
  411. "http", "example.com", "/",
  412. {"a" = "b"},
  413. {"http://example.com?a=b"},
  414. },
  415. {
  416. "http", "example.com", "/",
  417. {"a" = ""},
  418. {"http://example.com?a"},
  419. },
  420. {
  421. "http", "example.com", "/",
  422. {"a" = "b", "c" = "d"},
  423. {"http://example.com?a=b&c=d"},
  424. },
  425. {
  426. "http", "example.com", "/",
  427. {"a" = "", "c" = "d"},
  428. {"http://example.com?a&c=d"},
  429. },
  430. {
  431. "http", "example.com", "/example",
  432. {"a" = "", "b" = ""},
  433. {"http://example.com/example?a&b"},
  434. },
  435. {
  436. "https", "example.com", "/callback",
  437. {"redirect" = "https://other.com/login"},
  438. {"https://example.com/callback?redirect=https://other.com/login"},
  439. },
  440. }
  441. for test in test_cases {
  442. scheme, host, path, queries := net.split_url(test.url[0])
  443. defer {
  444. delete(queries)
  445. delete(test.queries)
  446. }
  447. msg := fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.scheme, scheme)
  448. expect(t, scheme == test.scheme, msg)
  449. msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.host, host)
  450. expect(t, host == test.host, msg)
  451. msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.path, path)
  452. expect(t, path == test.path, msg)
  453. msg = fmt.tprintf("Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries))
  454. expect(t, len(queries) == len(test.queries), msg)
  455. for k, v in queries {
  456. expected := test.queries[k]
  457. msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v)
  458. expect(t, v == expected, msg)
  459. }
  460. }
  461. }
  462. @test
  463. join_url_test :: proc(t: ^testing.T) {
  464. test_cases := []URL_Test{
  465. {
  466. "http", "example.com", "/",
  467. {},
  468. {"http://example.com/"},
  469. },
  470. {
  471. "https", "odin-lang.org", "/",
  472. {},
  473. {"https://odin-lang.org/"},
  474. },
  475. {
  476. "https", "odin-lang.org", "/docs/",
  477. {},
  478. {"https://odin-lang.org/docs/"},
  479. },
  480. {
  481. "https", "odin-lang.org", "/docs/overview",
  482. {},
  483. {"https://odin-lang.org/docs/overview"},
  484. },
  485. {
  486. "http", "example.com", "/",
  487. {"a" = "b"},
  488. {"http://example.com/?a=b"},
  489. },
  490. {
  491. "http", "example.com", "/",
  492. {"a" = ""},
  493. {"http://example.com/?a"},
  494. },
  495. {
  496. "http", "example.com", "/",
  497. {"a" = "b", "c" = "d"},
  498. {"http://example.com/?a=b&c=d", "http://example.com/?c=d&a=b"},
  499. },
  500. {
  501. "http", "example.com", "/",
  502. {"a" = "", "c" = "d"},
  503. {"http://example.com/?a&c=d", "http://example.com/?c=d&a"},
  504. },
  505. {
  506. "http", "example.com", "/example",
  507. {"a" = "", "b" = ""},
  508. {"http://example.com/example?a&b", "http://example.com/example?b&a"},
  509. },
  510. }
  511. for test in test_cases {
  512. url := net.join_url(test.scheme, test.host, test.path, test.queries)
  513. defer {
  514. delete(url)
  515. delete(test.queries)
  516. }
  517. pass := false
  518. for test_url in test.url {
  519. pass |= url == test_url
  520. }
  521. msg := fmt.tprintf("Expected `net.join_url` to return one of %s, got %s", test.url, url)
  522. expect(t, pass, msg)
  523. }
  524. }