library_check_test.v

4.9 KB · raw

1//! Phase 0 library checks.2//!3//! Koh 3 depends on five things outside its own source: SQLite (verified in4//! src/storage), Blake3, zlib, SHA-256/HMAC and an HTTP client. This file pins5//! the behaviour Koh relies on for the four stdlib ones, against vectors6//! produced by other implementations of the same algorithms. If a V upgrade7//! breaks an assumption, it breaks here rather than in a save.89module tests1011import compress.zlib12import crypto.blake313import crypto.hmac14import crypto.sha25615import net16import net.http17import os18import time1920// Koh 3 is built and tested on V master at b9a2d87, and that revision is the pin:21// V master moves, and a newer V may break Koh at any time. Building with any22// other compiler fails here rather than somewhere stranger.23fn test_built_with_the_pinned_v() {24	assert @VCURRENTHASH == 'b9a2d87'25}2627fn test_koh_refuses_a_build_without_garbage_collection() {28	output := os.join_path(os.temp_dir(), 'koh-no-gc-${os.getpid()}')29	result := os.execute('v -gc none -o "${output}" "${@VMODROOT}/src/koh.v"')30	assert result.exit_code != 031	assert result.output.contains('Koh requires garbage collection')32}3334// Vectors from the official blake3 bindings, cross-checked against the35// naming of real Koh 2 objects: `.koh/objects/<hash[0..2]>/<hash[2..]>` is36// blake3(uncompressed bytes) of a zlib stream.37fn test_blake3_vectors() {38	assert blake3.sum256(''.bytes()).hex() == 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262'39	assert blake3.sum256('abc'.bytes()).hex() == '6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85'40	assert blake3.sum256('The quick brown fox jumps over the lazy dog'.bytes()).hex() == '2f1514181aadccd913abd94cfa592701a5686ab23f8df1dff1b74710febc6d4a'41}4243fn test_blake3_streaming_matches_one_shot() {44	mut d := blake3.Digest.new_hash()!45	d.write('The quick brown '.bytes())!46	d.write('fox jumps over the lazy dog'.bytes())!47	assert d.checksum(32) == blake3.sum256('The quick brown fox jumps over the lazy dog'.bytes())48}4950fn test_zlib_round_trip() {51	// Object bytes must survive a trip through the store byte for byte,52	// including an empty payload and one with every byte value.53	mut all_bytes := []u8{len: 256}54	for i in 0 .. 256 {55		all_bytes[i] = u8(i)56	}57	mut payloads := [[]u8{}, 'hello koh'.bytes()]58	payloads << all_bytes59	for payload in payloads {60		packed := zlib.compress(payload)!61		assert zlib.decompress(packed)! == payload62	}63}6465fn test_sha256_and_hmac_vectors() {66	assert sha256.sum256('abc'.bytes()).hex() == 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'67	assert sha256.hexhash('abc') == 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'68	// HMAC-SHA256, the primitive behind SigV4 signing.69	mac := hmac.new('key'.bytes(), 'The quick brown fox jumps over the lazy dog'.bytes(),70		sha256.sum256, 64)71	assert mac.hex() == 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8'72}7374struct EtagHandler {75mut:76	seen_if_none_match string77}7879fn (mut h EtagHandler) handle(req http.Request) http.Response {80	h.seen_if_none_match = req.header.get_custom('If-None-Match', http.HeaderQueryConfig{}) or {81		''82	}83	mut resp := http.Response{84		body: 'payload'85	}86	if h.seen_if_none_match == '*' {87		resp.status_code = 41288		resp.status_msg = 'Precondition Failed'89	} else {90		resp.status_code = 20091		resp.status_msg = 'OK'92	}93	resp.header.add_custom('ETag', '"3b1f"') or {}94	return resp95}9697// Starts the probe server on the first free port, so parallel test runs do not98// fight over a fixed one.99fn start_probe_server() !(int, &http.Server) {100	for port in [19301, 19302, 19303, 19304, 19305] {101		listener := net.listen_tcp(.ip, '127.0.0.1:${port}') or { continue }102		mut server := &http.Server{103			listener:             listener104			handler:              EtagHandler{}105			show_startup_message: false106		}107		spawn server.listen_and_serve()108		server.wait_till_running()!109		return port, server110	}111	return error('no free port for the probe server')112}113114// The S3 backend needs custom request headers, the response status, and the115// ETag that conditional writes are keyed on. TLS itself is exercised against a116// live bucket, not here.117fn test_http_client_sends_headers_and_reads_status_and_etag() {118	port, mut server := start_probe_server()!119	defer {120		server.stop()121	}122	url := 'http://127.0.0.1:${port}/probe'123	mut hdr := http.new_header()124	hdr.add_custom('If-None-Match', '*')!125126	resp := http.fetch(127		url:    url128		method: .get129		header: hdr130	)!131	assert resp.status_code == 412132	assert resp.header.get_custom('ETag', http.HeaderQueryConfig{}) or { '' } == '"3b1f"'133134	plain := http.fetch(url: url, method: .get)!135	assert plain.status_code == 200136	assert plain.body == 'payload'137138	// Retries: the S3 backend retries network errors and 5xx. A dead port must139	// surface as an error rather than a hang.140	if _ := http.fetch(141		url:          'http://127.0.0.1:19399/gone'142		method:       .get143		read_timeout: 2 * time.second144	)145	{146		assert false, 'a request to a closed port should fail'147	}148}