RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / libc.rs
3524 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Stage 15, the plumbing a C library needs: threads and futexes, poll and
//! epoll, nonblocking I/O, loopback networking, ttys and ptys. As in
//! stage 14 the detailed checks live in user programs (threads,
//! echoserver, ptytest, nbtest: exit code = failed checks); this file runs
//! them and adds what only the kernel can see: nothing is left behind
//! (threads, processes, futex waiters, ptys, sockets, memory frames).
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(rustbelt::test_runner)]
#![reexport_test_harness_main = "test_main"]

extern crate alloc;

use alloc::string::String;
use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use rustbelt::process::{self, ExitStatus, SpawnArgs};
use rustbelt::{fs, futex, memory, net, thread, tty};

entry_point!(main);

fn main(boot_info: &'static BootInfo) -> ! {
    rustbelt::init();
    rustbelt::init_memory(boot_info);
    rustbelt::init_smp(); // all 4 CPUs run threads
    fs::init();
    rustbelt::init_storage();
    rustbelt::init_net(); // brings up 127.0.0.1 (and eth0 if QEMU has one)
    test_main();
    rustbelt::hlt_loop();
}

fn run(name: &str, args: &[&str]) -> ExitStatus {
    let mut spawn = SpawnArgs::kernel(name);
    spawn.args.extend(args.iter().map(|a| String::from(*a)));
    let p = process::spawn_path(&alloc::format!("/mnt/disk0/bin/{}", name), spawn).expect("spawn failed");
    process::wait(&p)
}

/// Snapshot of everything a buggy program could leak.
#[derive(Debug, PartialEq)]
struct Counts {
    threads: usize,
    processes: usize,
    futex_waiters: usize,
    ptys: usize,
    sockets: usize,
    frames: usize,
}

fn counts() -> Counts {
    thread::sleep_ticks(3); // let exited threads' stacks be freed
    Counts {
        threads: thread::thread_count(),
        processes: process::count(),
        futex_waiters: futex::waiters(),
        ptys: tty::pty_count(),
        sockets: net::socket_count(),
        frames: memory::frames_in_use(),
    }
}

/// Run a test program twice: once to warm caches, then again to check that
/// it passes and that every count comes back to where it was.
fn run_without_leaks(name: &str) {
    assert_eq!(run(name, &[]), ExitStatus::Exited(0), "{} failed (warm-up)", name);
    let before = counts();
    assert_eq!(run(name, &[]), ExitStatus::Exited(0), "{} failed", name);
    let after = counts();
    assert_eq!(after, before, "{} leaked something", name);
    assert_eq!(after.futex_waiters, 0, "futex waiters left behind");
    assert_eq!(after.ptys, 0, "ptys left behind");
}

/// 4 threads sum 1..=40,000,000 under a futex mutex, on all 4 CPUs.
#[test_case]
fn threads_and_futexes() {
    run_without_leaks("threads");
}

/// One epoll and one poll server over 127.0.0.1, three forked clients each.
#[test_case]
fn poll_epoll_echo_over_loopback() {
    run_without_leaks("echoserver");
}

/// pty round trips, canonical vs raw, ^C -> SIGINT, hangup on close.
#[test_case]
fn ptys_and_line_discipline() {
    run_without_leaks("ptytest");
}

/// EAGAIN everywhere, fcntl/FIONBIO, writev/pread/pwrite, clocks.
#[test_case]
fn nonblocking_io_and_libc_extras() {
    run_without_leaks("nbtest");
}

/// Loopback works without DHCP and without the NIC: 127.0.0.1 is local
/// whatever eth0 is doing.
#[test_case]
fn loopback_is_always_there() {
    assert!(net::is_loopback([127, 0, 0, 1]));
    assert!(!net::is_loopback([10, 0, 2, 15]));
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    rustbelt::test_panic_handler(info)
}