library_check_test.v

6.1 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}3334fn test_linux_release_script_builds_without_zig() {35	dir := os.join_path(os.temp_dir(), 'koh-release-script-${time.now().unix_nano()}')36	tools := os.join_path(dir, 'tools')37	os.mkdir_all(tools)!38	for name in ['dirname', 'mkdir', 'cc'] {39		os.symlink(os.find_abs_path_of_executable(name)!, os.join_path(tools, name))!40	}41	os.cp('${@VMODROOT}/build.sh', os.join_path(dir, 'build.sh'))!42	v := os.join_path(tools, 'v')43	os.write_file(v, '#!/bin/sh44if [ "$1" = version ]; then echo "V 0.5.2 b9a2d87"; exit 0; fi45printf "%s\\n" "$@" > args46while [ "$#" -gt 0 ]; do47  if [ "$1" = -o ]; then : > "$2"; exit 0; fi48  shift49done50exit 151')!52	os.chmod(v, 0o755)!53	for cpu in ['aarch64', 'x86_64'] {54		uname := os.join_path(tools, 'uname')55		os.write_file(uname, '#!/bin/sh\ncase "$1" in -s) echo Linux ;; -m) echo ${cpu} ;; esac\n')!56		os.chmod(uname, 0o755)!57		result := os.execute('PATH="${tools}" KOH_TARGET=linux-${cpu} /bin/sh "${dir}/build.sh"')58		assert result.exit_code == 0, result.output59		assert os.is_file(os.join_path(dir, 'build/releases/koh-linux-${cpu}'))60		args := os.read_file(os.join_path(dir, 'args'))!61		assert args.contains('-gc\nboehm\n')62		assert args.contains('-d\nuse_bundled_libgc\n')63		assert args.contains('-ldflags\n-static -s\n')64	}65}6667// Vectors from the official blake3 bindings, cross-checked against the68// naming of real Koh 2 objects: `.koh/objects/<hash[0..2]>/<hash[2..]>` is69// blake3(uncompressed bytes) of a zlib stream.70fn test_blake3_vectors() {71	assert blake3.sum256(''.bytes()).hex() == 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262'72	assert blake3.sum256('abc'.bytes()).hex() == '6437b3ac38465133ffb63b75273a8db548c558465d79db03fd359c6cd5bd9d85'73	assert blake3.sum256('The quick brown fox jumps over the lazy dog'.bytes()).hex() == '2f1514181aadccd913abd94cfa592701a5686ab23f8df1dff1b74710febc6d4a'74}7576fn test_blake3_streaming_matches_one_shot() {77	mut d := blake3.Digest.new_hash()!78	d.write('The quick brown '.bytes())!79	d.write('fox jumps over the lazy dog'.bytes())!80	assert d.checksum(32) == blake3.sum256('The quick brown fox jumps over the lazy dog'.bytes())81}8283fn test_zlib_round_trip() {84	// Object bytes must survive a trip through the store byte for byte,85	// including an empty payload and one with every byte value.86	mut all_bytes := []u8{len: 256}87	for i in 0 .. 256 {88		all_bytes[i] = u8(i)89	}90	mut payloads := [[]u8{}, 'hello koh'.bytes()]91	payloads << all_bytes92	for payload in payloads {93		packed := zlib.compress(payload)!94		assert zlib.decompress(packed)! == payload95	}96}9798fn test_sha256_and_hmac_vectors() {99	assert sha256.sum256('abc'.bytes()).hex() == 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'100	assert sha256.hexhash('abc') == 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'101	// HMAC-SHA256, the primitive behind SigV4 signing.102	mac := hmac.new('key'.bytes(), 'The quick brown fox jumps over the lazy dog'.bytes(),103		sha256.sum256, 64)104	assert mac.hex() == 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8'105}106107struct EtagHandler {108mut:109	seen_if_none_match string110}111112fn (mut h EtagHandler) handle(req http.Request) http.Response {113	h.seen_if_none_match = req.header.get_custom('If-None-Match', http.HeaderQueryConfig{}) or {114		''115	}116	mut resp := http.Response{117		body: 'payload'118	}119	if h.seen_if_none_match == '*' {120		resp.status_code = 412121		resp.status_msg = 'Precondition Failed'122	} else {123		resp.status_code = 200124		resp.status_msg = 'OK'125	}126	resp.header.add_custom('ETag', '"3b1f"') or {}127	return resp128}129130// Starts the probe server on the first free port, so parallel test runs do not131// fight over a fixed one.132fn start_probe_server() !(int, &http.Server) {133	for port in [19301, 19302, 19303, 19304, 19305] {134		listener := net.listen_tcp(.ip, '127.0.0.1:${port}') or { continue }135		mut server := &http.Server{136			listener:             listener137			handler:              EtagHandler{}138			show_startup_message: false139		}140		spawn server.listen_and_serve()141		server.wait_till_running()!142		return port, server143	}144	return error('no free port for the probe server')145}146147// The S3 backend needs custom request headers, the response status, and the148// ETag that conditional writes are keyed on. TLS itself is exercised against a149// live bucket, not here.150fn test_http_client_sends_headers_and_reads_status_and_etag() {151	port, mut server := start_probe_server()!152	defer {153		server.stop()154	}155	url := 'http://127.0.0.1:${port}/probe'156	mut hdr := http.new_header()157	hdr.add_custom('If-None-Match', '*')!158159	resp := http.fetch(160		url:    url161		method: .get162		header: hdr163	)!164	assert resp.status_code == 412165	assert resp.header.get_custom('ETag', http.HeaderQueryConfig{}) or { '' } == '"3b1f"'166167	plain := http.fetch(url: url, method: .get)!168	assert plain.status_code == 200169	assert plain.body == 'payload'170171	// Retries: the S3 backend retries network errors and 5xx. A dead port must172	// surface as an error rather than a hang.173	if _ := http.fetch(174		url:          'http://127.0.0.1:19399/gone'175		method:       .get176		read_timeout: 2 * time.second177	)178	{179		assert false, 'a request to a closed port should fail'180	}181}