Skip to content

Commit a2ecd4b

Browse files
fzaiserfanzier
andauthored
Add async/await examples from tokio's test to a new "slow" test suite (rust-lang#1332)
* Make tokio tests compile with Kani * Add expected file * Lower unwind bound * Raise unwind bounds for some tests * Disable tests that hit bugs or take too long * Disable failing tests * Add a "slow" test suite * Add Github action to run slow tests every night * Fix tests and expected * Address review comments * Rerun CI Co-authored-by: Fabian Zaiser <[email protected]>
1 parent 0a987ff commit a2ecd4b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3396
-0
lines changed

.github/workflows/slow-tests.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
name: Kani's slow tests
4+
on:
5+
schedule:
6+
- cron: "30 5 * * *" # Run this every day at 05:30 UTC
7+
8+
env:
9+
RUST_BACKTRACE: 1
10+
11+
jobs:
12+
regression:
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os: [macos-11, ubuntu-18.04, ubuntu-20.04]
17+
steps:
18+
- name: Checkout Kani
19+
uses: actions/checkout@v2
20+
21+
- name: Setup Kani Dependencies
22+
uses: ./.github/actions/setup
23+
with:
24+
os: ${{ matrix.os }}
25+
26+
- name: Build Kani
27+
run: cargo build
28+
29+
- name: Run Kani's slow tests
30+
run: ./scripts/kani-slow-tests.sh

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,5 @@ exclude = [
6464
"tests/cargo-kani",
6565
"tests/perf",
6666
"tests/cargo-ui",
67+
"tests/slow",
6768
]

scripts/kani-slow-tests.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# Copyright Kani Contributors
3+
# SPDX-License-Identifier: Apache-2.0 OR MIT
4+
5+
if [[ -z $KANI_REGRESSION_KEEP_GOING ]]; then
6+
set -o errexit
7+
fi
8+
set -o pipefail
9+
set -o nounset
10+
11+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
12+
export PATH=$SCRIPT_DIR:$PATH
13+
EXTRA_X_PY_BUILD_ARGS="${EXTRA_X_PY_BUILD_ARGS:-}"
14+
KANI_DIR=$SCRIPT_DIR/..
15+
16+
# This variable forces an error when there is a mismatch on the expected
17+
# descriptions from cbmc checks.
18+
# TODO: We should add a more robust mechanism to detect python unexpected behavior.
19+
export KANI_FAIL_ON_UNEXPECTED_DESCRIPTION="true"
20+
21+
# Build all packages in the workspace
22+
cargo build
23+
24+
# Run slow compiletests
25+
cargo run -p compiletest --quiet -- --suite slow --mode cargo-kani
26+
27+
echo
28+
echo "Kani slow tests completed successfully."
29+
echo

tests/slow/tokio-proofs/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
[package]
4+
name = "kani_tokio"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
[features]
9+
default = ["full"]
10+
full = []
11+
12+
[dependencies]
13+
tokio = { version = "1.20", features = ["full"] }
14+
tokio-test = "0.4.0"
15+
tokio-stream = "0.1"
16+
futures = { version = "0.3.0", features = ["async-await"] }
17+
bytes = "1.2.1"
18+
tokio-util = { version = "0.7.3", features = ["io"] }
19+
async-stream = "0.3.3"
20+
# mockall = "0.11.1"
21+
# async-stream = "0.3"

tests/slow/tokio-proofs/expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Complete - 13 successfully verified harnesses, 0 failures, 13 total.

tests/slow/tokio-proofs/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
mod tokio;
5+
mod tokio_stream;
6+
mod tokio_test;
7+
mod tokio_util;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
//
3+
// Modifications Copyright Kani Contributors
4+
// See GitHub history for details.
5+
6+
// Original copyright tokio contributors.
7+
// origin: tokio/tests/tokio/ at commit b2ada60e701d5c9e6644cf8fc42a100774f8e23f
8+
9+
#![warn(rust_2018_idioms)]
10+
#![cfg(feature = "full")]
11+
12+
use tokio::io::AsyncReadExt;
13+
use tokio_test::assert_ok;
14+
15+
#[cfg(disabled)] // because it timed out after 2h
16+
#[kani::proof]
17+
#[kani::unwind(2)]
18+
async fn chain() {
19+
let mut buf = Vec::new();
20+
let rd1: &[u8] = b"hello ";
21+
let rd2: &[u8] = b"world";
22+
23+
let mut rd = rd1.chain(rd2);
24+
assert_ok!(rd.read_to_end(&mut buf).await);
25+
assert_eq!(buf, b"hello world");
26+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
//
3+
// Modifications Copyright Kani Contributors
4+
// See GitHub history for details.
5+
6+
// Original copyright tokio contributors.
7+
// origin: tokio/tests/tokio/ at commit b2ada60e701d5c9e6644cf8fc42a100774f8e23f
8+
9+
#![warn(rust_2018_idioms)]
10+
#![cfg(feature = "full")]
11+
12+
use bytes::BytesMut;
13+
use futures::ready;
14+
use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
15+
use tokio_test::assert_ok;
16+
17+
use std::pin::Pin;
18+
use std::task::{Context, Poll};
19+
20+
#[cfg(disabled)] // because it timed out after 2h
21+
#[kani::proof]
22+
#[kani::unwind(2)]
23+
async fn copy() {
24+
struct Rd(bool);
25+
26+
impl AsyncRead for Rd {
27+
fn poll_read(
28+
mut self: Pin<&mut Self>,
29+
_cx: &mut Context<'_>,
30+
buf: &mut ReadBuf<'_>,
31+
) -> Poll<io::Result<()>> {
32+
if self.0 {
33+
buf.put_slice(b"hello world");
34+
self.0 = false;
35+
Poll::Ready(Ok(()))
36+
} else {
37+
Poll::Ready(Ok(()))
38+
}
39+
}
40+
}
41+
42+
let mut rd = Rd(true);
43+
let mut wr = Vec::new();
44+
45+
let n = assert_ok!(io::copy(&mut rd, &mut wr).await);
46+
assert_eq!(n, 11);
47+
assert_eq!(wr, b"hello world");
48+
}
49+
50+
#[cfg(disabled)] // because it timed out after 2h
51+
#[kani::proof]
52+
#[kani::unwind(2)]
53+
async fn proxy() {
54+
struct BufferedWd {
55+
buf: BytesMut,
56+
writer: io::DuplexStream,
57+
}
58+
59+
impl AsyncWrite for BufferedWd {
60+
fn poll_write(
61+
self: Pin<&mut Self>,
62+
_cx: &mut Context<'_>,
63+
buf: &[u8],
64+
) -> Poll<io::Result<usize>> {
65+
self.get_mut().buf.extend_from_slice(buf);
66+
Poll::Ready(Ok(buf.len()))
67+
}
68+
69+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
70+
let this = self.get_mut();
71+
72+
while !this.buf.is_empty() {
73+
let n = ready!(Pin::new(&mut this.writer).poll_write(cx, &this.buf))?;
74+
let _ = this.buf.split_to(n);
75+
}
76+
77+
Pin::new(&mut this.writer).poll_flush(cx)
78+
}
79+
80+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
81+
Pin::new(&mut self.writer).poll_shutdown(cx)
82+
}
83+
}
84+
85+
let (rd, wd) = io::duplex(1024);
86+
let mut rd = rd.take(1024);
87+
let mut wd = BufferedWd { buf: BytesMut::new(), writer: wd };
88+
89+
// write start bytes
90+
assert_ok!(wd.write_all(&[0x42; 512]).await);
91+
assert_ok!(wd.flush().await);
92+
93+
let n = assert_ok!(io::copy(&mut rd, &mut wd).await);
94+
95+
assert_eq!(n, 1024);
96+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
//
3+
// Modifications Copyright Kani Contributors
4+
// See GitHub history for details.
5+
6+
// Original copyright tokio contributors.
7+
// origin: tokio/tests/tokio/ at commit b2ada60e701d5c9e6644cf8fc42a100774f8e23f
8+
9+
#![warn(rust_2018_idioms)]
10+
#![cfg(feature = "full")]
11+
12+
use tokio::io::AsyncBufReadExt;
13+
use tokio_test::assert_ok;
14+
15+
#[cfg(disabled)] // because it timed out after 2h
16+
#[kani::proof]
17+
#[kani::unwind(2)]
18+
async fn lines_inherent() {
19+
let rd: &[u8] = b"hello\r\nworld\n\n";
20+
let mut st = rd.lines();
21+
22+
let b = assert_ok!(st.next_line().await).unwrap();
23+
assert_eq!(b, "hello");
24+
let b = assert_ok!(st.next_line().await).unwrap();
25+
assert_eq!(b, "world");
26+
let b = assert_ok!(st.next_line().await).unwrap();
27+
assert_eq!(b, "");
28+
assert!(assert_ok!(st.next_line().await).is_none());
29+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
//
3+
// Modifications Copyright Kani Contributors
4+
// See GitHub history for details.
5+
6+
// Original copyright tokio contributors.
7+
// origin: tokio/tests/tokio/ at commit b2ada60e701d5c9e6644cf8fc42a100774f8e23f
8+
9+
#![warn(rust_2018_idioms)]
10+
#![cfg(feature = "full")]
11+
12+
use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
13+
14+
#[cfg(disabled)] // because it timed out after 2h
15+
#[kani::proof]
16+
#[kani::unwind(2)]
17+
async fn ping_pong() {
18+
let (mut a, mut b) = duplex(32);
19+
20+
let mut buf = [0u8; 4];
21+
22+
a.write_all(b"ping").await.unwrap();
23+
b.read_exact(&mut buf).await.unwrap();
24+
assert_eq!(&buf, b"ping");
25+
26+
b.write_all(b"pong").await.unwrap();
27+
a.read_exact(&mut buf).await.unwrap();
28+
assert_eq!(&buf, b"pong");
29+
}
30+
31+
// Kani does not support this one yet because it uses spawn
32+
#[cfg(disabled)] // because it timed out after 2h
33+
#[kani::proof]
34+
#[kani::unwind(2)]
35+
async fn across_tasks() {
36+
let (mut a, mut b) = duplex(32);
37+
38+
let t1 = tokio::spawn(async move {
39+
a.write_all(b"ping").await.unwrap();
40+
let mut buf = [0u8; 4];
41+
a.read_exact(&mut buf).await.unwrap();
42+
assert_eq!(&buf, b"pong");
43+
});
44+
45+
let t2 = tokio::spawn(async move {
46+
let mut buf = [0u8; 4];
47+
b.read_exact(&mut buf).await.unwrap();
48+
assert_eq!(&buf, b"ping");
49+
b.write_all(b"pong").await.unwrap();
50+
});
51+
52+
t1.await.unwrap();
53+
t2.await.unwrap();
54+
}
55+
56+
// Kani does not support this one yet because it uses spawn
57+
#[cfg(disabled)] // because it timed out after 2h
58+
#[kani::proof]
59+
#[kani::unwind(2)]
60+
async fn disconnect() {
61+
let (mut a, mut b) = duplex(32);
62+
63+
let t1 = tokio::spawn(async move {
64+
a.write_all(b"ping").await.unwrap();
65+
// and dropped
66+
});
67+
68+
let t2 = tokio::spawn(async move {
69+
let mut buf = [0u8; 32];
70+
let n = b.read(&mut buf).await.unwrap();
71+
assert_eq!(&buf[..n], b"ping");
72+
73+
let n = b.read(&mut buf).await.unwrap();
74+
assert_eq!(n, 0);
75+
});
76+
77+
t1.await.unwrap();
78+
t2.await.unwrap();
79+
}
80+
81+
// Kani does not support this one yet because it uses spawn
82+
#[cfg(disabled)] // because it timed out after 2h
83+
#[kani::proof]
84+
#[kani::unwind(2)]
85+
async fn disconnect_reader() {
86+
let (a, mut b) = duplex(2);
87+
88+
let t1 = tokio::spawn(async move {
89+
// this will block, as not all data fits into duplex
90+
b.write_all(b"ping").await.unwrap_err();
91+
});
92+
93+
let t2 = tokio::spawn(async move {
94+
// here we drop the reader side, and we expect the writer in the other
95+
// task to exit with an error
96+
drop(a);
97+
});
98+
99+
t2.await.unwrap();
100+
t1.await.unwrap();
101+
}
102+
103+
// Kani does not support this one yet because it uses spawn
104+
#[cfg(disabled)] // because it timed out after 2h
105+
#[kani::proof]
106+
#[kani::unwind(2)]
107+
async fn max_write_size() {
108+
let (mut a, mut b) = duplex(32);
109+
110+
let t1 = tokio::spawn(async move {
111+
let n = a.write(&[0u8; 64]).await.unwrap();
112+
assert_eq!(n, 32);
113+
let n = a.write(&[0u8; 64]).await.unwrap();
114+
assert_eq!(n, 4);
115+
});
116+
117+
let mut buf = [0u8; 4];
118+
b.read_exact(&mut buf).await.unwrap();
119+
120+
t1.await.unwrap();
121+
122+
// drop b only after task t1 finishes writing
123+
drop(b);
124+
}
125+
126+
// Kani does not support this one yet because it uses select
127+
#[cfg(disabled)] // because it timed out after 2h
128+
#[kani::proof]
129+
#[kani::unwind(2)]
130+
async fn duplex_is_cooperative() {
131+
let (mut tx, mut rx) = tokio::io::duplex(1024 * 8);
132+
133+
tokio::select! {
134+
biased;
135+
136+
_ = async {
137+
loop {
138+
let buf = [3u8; 4096];
139+
tx.write_all(&buf).await.unwrap();
140+
let mut buf = [0u8; 4096];
141+
rx.read(&mut buf).await.unwrap();
142+
}
143+
} => {},
144+
_ = tokio::task::yield_now() => {}
145+
}
146+
}

0 commit comments

Comments
 (0)