aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2021-03-13 14:39:14 -0500
committerJesse Luehrs <doy@tozt.net>2021-03-13 14:39:14 -0500
commitf0c07dfee8b65a36033943f888c1da5f8ec96098 (patch)
tree4382c61dc122c639b4cd062abdd42a43b2cc5555
parent5244bfcd2b67ab6bf808750326de1e08e52c4f2f (diff)
downloadtextmode-f0c07dfee8b65a36033943f888c1da5f8ec96098.tar.gz
textmode-f0c07dfee8b65a36033943f888c1da5f8ec96098.zip
clean up examples to make a bare `cargo test` work
-rw-r--r--examples/async.rs32
-rw-r--r--examples/basic.rs34
-rw-r--r--examples/tmux.rs390
-rw-r--r--examples/tmux_impl/mod.rs379
-rw-r--r--tests/basic.rs2
l---------tests/fixtures/bin/src/bin/async.rs1
6 files changed, 424 insertions, 414 deletions
diff --git a/examples/async.rs b/examples/async.rs
deleted file mode 100644
index 52c1edb..0000000
--- a/examples/async.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use textmode::Textmode as _;
-
-async fn run(
- tm: &mut textmode::Output,
- input: &mut textmode::Input,
-) -> textmode::Result<()> {
- tm.move_to(5, 5);
- tm.write_str("foo");
- input.read_key().await?;
- tm.refresh().await?;
- input.read_key().await?;
-
- tm.move_to(8, 8);
- tm.set_fgcolor(textmode::color::GREEN);
- tm.write_str("bar");
- tm.move_to(11, 11);
- tm.set_fgcolor(vt100::Color::Default);
- tm.write_str("baz");
- input.read_key().await?;
- tm.refresh().await?;
- input.read_key().await?;
- Ok(())
-}
-
-fn main() {
- smol::block_on(async {
- let mut input = textmode::Input::new().await.unwrap();
- let mut tm = textmode::Output::new().await.unwrap();
- let e = run(&mut tm, &mut input).await;
- e.unwrap();
- });
-}
diff --git a/examples/basic.rs b/examples/basic.rs
index 18d86a9..49f56bc 100644
--- a/examples/basic.rs
+++ b/examples/basic.rs
@@ -1,5 +1,39 @@
use textmode::Textmode as _;
+#[cfg(feature = "async")]
+async fn run(
+ tm: &mut textmode::Output,
+ input: &mut textmode::Input,
+) -> textmode::Result<()> {
+ tm.move_to(5, 5);
+ tm.write_str("foo");
+ input.read_key().await?;
+ tm.refresh().await?;
+ input.read_key().await?;
+
+ tm.move_to(8, 8);
+ tm.set_fgcolor(textmode::color::GREEN);
+ tm.write_str("bar");
+ tm.move_to(11, 11);
+ tm.set_fgcolor(vt100::Color::Default);
+ tm.write_str("baz");
+ input.read_key().await?;
+ tm.refresh().await?;
+ input.read_key().await?;
+ Ok(())
+}
+
+#[cfg(feature = "async")]
+fn main() {
+ smol::block_on(async {
+ let mut input = textmode::Input::new().await.unwrap();
+ let mut tm = textmode::Output::new().await.unwrap();
+ let e = run(&mut tm, &mut input).await;
+ e.unwrap();
+ });
+}
+
+#[cfg(not(feature = "async"))]
fn main() {
let mut tm = textmode::blocking::Output::new().unwrap();
let mut input = textmode::blocking::Input::new().unwrap();
diff --git a/examples/tmux.rs b/examples/tmux.rs
index c3e16b4..5eebee4 100644
--- a/examples/tmux.rs
+++ b/examples/tmux.rs
@@ -1,389 +1,19 @@
-use pty_process::Command as _;
-use smol::io::{AsyncReadExt as _, AsyncWriteExt as _};
-use textmode::Textmode as _;
-
-enum Command {
- NewWindow,
- NextWindow,
-}
-
-enum Event {
- Input(textmode::Key),
- Output,
- WindowExit(usize),
- Command(Command),
- Notification,
-}
-
-struct Window {
- child: std::sync::Arc<pty_process::smol::Child>,
- vt: std::sync::Arc<smol::lock::Mutex<vt100::Parser>>,
- screen: vt100::Screen,
-}
-
-#[derive(Clone)]
-struct Notification {
- text: String,
- expiry: std::time::Instant,
-}
-
-struct State {
- windows: std::collections::BTreeMap<usize, Window>,
- current_window: usize,
- next_window_id: usize,
- notifications: std::collections::BTreeMap<usize, Notification>,
- next_notification_id: usize,
- wevents: smol::channel::Sender<Event>,
- revents: smol::channel::Receiver<Event>,
-}
-
-impl State {
- fn new() -> Self {
- let (sender, receiver) = smol::channel::unbounded();
- Self {
- windows: std::collections::BTreeMap::new(),
- current_window: 0,
- next_window_id: 0,
- notifications: std::collections::BTreeMap::new(),
- next_notification_id: 0,
- wevents: sender,
- revents: receiver,
- }
- }
-
- fn current_window(&self) -> &Window {
- &self.windows[&self.current_window]
- }
-
- fn current_window_mut(&mut self) -> &mut Window {
- self.windows.get_mut(&self.current_window).unwrap()
- }
-
- fn next_window(&mut self, ex: &smol::Executor<'_>) {
- self.current_window = self
- .windows
- .keys()
- .copied()
- .cycle()
- .skip_while(|&id| id < self.current_window)
- .nth(1)
- .unwrap();
- self.notify(
- ex,
- &format!("switched to window {}", self.current_window),
- );
- }
-
- fn notify(&mut self, ex: &smol::Executor<'_>, text: &str) {
- let now = std::time::Instant::now();
- let expiry = now + std::time::Duration::from_secs(5);
- let text = text.to_string();
- let notification = Notification { text, expiry };
- let id = self.next_notification_id;
- self.next_notification_id += 1;
- self.notifications.insert(id, notification);
- let notify = self.wevents.clone();
- ex.spawn(async move {
- smol::Timer::at(expiry).await;
- notify.send(Event::Notification).await.unwrap();
- })
- .detach();
- }
-
- fn spawn_input_task(
- &self,
- ex: &smol::Executor<'_>,
- mut input: textmode::Input,
- ) {
- let notify = self.wevents.clone();
- ex.spawn(async move {
- let mut waiting_for_command = false;
- input.parse_utf8(false);
- input.parse_meta(false);
- input.parse_special_keys(false);
- loop {
- input.parse_single(waiting_for_command);
- match input.read_key().await {
- Ok(Some(key)) => {
- if waiting_for_command {
- waiting_for_command = false;
- match key {
- textmode::Key::Ctrl(b'n') => {
- notify
- .send(Event::Input(key))
- .await
- .unwrap();
- }
- textmode::Key::Byte(b'c') => {
- notify
- .send(Event::Command(
- Command::NewWindow,
- ))
- .await
- .unwrap();
- }
- textmode::Key::Byte(b'n') => {
- notify
- .send(Event::Command(
- Command::NextWindow,
- ))
- .await
- .unwrap();
- }
- _ => {} // ignore
- }
- } else {
- match key {
- textmode::Key::Ctrl(b'n') => {
- waiting_for_command = true;
- }
- _ => {
- notify
- .send(Event::Input(key))
- .await
- .unwrap();
- }
- }
- }
- }
- Ok(None) => {
- break;
- }
- Err(e) => {
- eprintln!("{}", e);
- break;
- }
- }
- }
- })
- .detach();
- }
-
- fn new_window(
- &mut self,
- ex: &smol::Executor<'_>,
- notify: smol::channel::Sender<Event>,
- ) {
- let child = smol::process::Command::new("zsh")
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
- let child = std::sync::Arc::new(child);
- let vt = vt100::Parser::default();
- let screen = vt.screen().clone();
- let vt = std::sync::Arc::new(smol::lock::Mutex::new(vt));
- let id = self.next_window_id;
- self.next_window_id += 1;
- let window = Window {
- child: child.clone(),
- vt: vt.clone(),
- screen,
- };
- self.windows.insert(id, window);
- self.current_window = id;
- self.notify(ex, &format!("created window {}", id));
- ex.spawn(async move {
- let mut buf = [0_u8; 4096];
- loop {
- match child.pty().read(&mut buf).await {
- Ok(bytes) => {
- vt.lock_arc().await.process(&buf[..bytes]);
- notify.send(Event::Output).await.unwrap();
- }
- Err(e) => {
- // EIO means that the process closed the other
- // end of the pty
- if e.raw_os_error() != Some(libc::EIO) {
- eprintln!("pty read failed: {:?}", e);
- }
- notify.send(Event::WindowExit(id)).await.unwrap();
- break;
- }
- }
- }
- })
- .detach();
- }
-
- async fn redraw_current_window(&mut self, tm: &mut textmode::Output) {
- let window = self.current_window();
- tm.clear();
- let new_screen = window.vt.lock_arc().await.screen().clone();
- tm.write(&new_screen.state_formatted());
- self.draw_notifications(tm, &new_screen);
- tm.refresh().await.unwrap();
- }
-
- async fn update_current_window(&mut self, tm: &mut textmode::Output) {
- let window = self.current_window();
- let old_screen = window.screen.clone();
- let new_screen = window.vt.lock_arc().await.screen().clone();
- let diff = new_screen.state_diff(&old_screen);
- self.clear_notifications(tm, &old_screen);
- tm.write(&diff);
- self.draw_notifications(tm, &new_screen);
- tm.refresh().await.unwrap();
- self.current_window_mut().screen = new_screen;
- }
-
- fn clear_notifications(
- &mut self,
- tm: &mut textmode::Output,
- screen: &vt100::Screen,
- ) {
- if self.notifications.is_empty() {
- return;
- }
-
- let reset_attrs = screen.attributes_formatted();
- let pos = screen.cursor_position();
- for (i, row) in screen
- .rows_formatted(0, 80)
- .enumerate()
- .take(self.notifications.len())
- {
- tm.move_to(i as u16, 0);
- tm.reset_attributes();
- tm.clear_line();
- tm.write(&row);
- }
- tm.move_to(pos.0, pos.1);
- tm.write(&reset_attrs);
- }
-
- fn draw_notifications(
- &mut self,
- tm: &mut textmode::Output,
- screen: &vt100::Screen,
- ) {
- if self.notifications.is_empty() {
- return;
- }
-
- let now = std::time::Instant::now();
- self.notifications = self
- .notifications
- .iter()
- .map(|(k, v)| (*k, v.clone()))
- .filter(|(_, v)| v.expiry >= now)
- .collect();
-
- if self.notifications.is_empty() {
- return;
- }
-
- let reset_attrs = screen.attributes_formatted();
- let pos = screen.cursor_position();
- tm.reset_attributes();
- tm.set_bgcolor(textmode::color::CYAN);
- tm.set_fgcolor(textmode::color::WHITE);
- for (i, notification) in self.notifications.values().enumerate() {
- tm.move_to(i as u16, 0);
- tm.clear_line();
- let str_len = notification.text.len();
- let spaces = 80 - str_len;
- let prefix_spaces = spaces / 2;
- tm.write(&vec![b' '; prefix_spaces]);
- tm.write_str(&notification.text);
- }
- tm.move_to(pos.0, pos.1);
- tm.write(&reset_attrs);
- }
-}
-
-#[must_use]
-struct Tmux {
- input: textmode::Input,
- tm: textmode::Output,
- state: State,
-}
-
-impl Tmux {
- async fn new() -> Self {
- let input = textmode::Input::new().await.unwrap();
- let tm = textmode::Output::new().await.unwrap();
- let state = State::new();
- Self { input, tm, state }
- }
-
- async fn run(self, ex: &smol::Executor<'_>) {
- let Self {
- input,
- mut tm,
- mut state,
- } = self;
-
- state.spawn_input_task(ex, input);
-
- ex.run(async {
- state.new_window(ex, state.wevents.clone());
-
- loop {
- match state.revents.recv().await {
- Ok(Event::Output) => {
- state.update_current_window(&mut tm).await;
- }
- Ok(Event::Input(key)) => {
- state
- .current_window()
- .child
- .pty()
- .write_all(&key.into_bytes())
- .await
- .unwrap();
- }
- Ok(Event::WindowExit(id)) => {
- // do this first because next_window breaks if
- // current_window is greater than all existing windows
- if state.current_window == id {
- state.next_window(ex)
- }
- let mut dropped_window =
- state.windows.remove(&id).unwrap();
- // i can get_mut because at this point the future
- // holding the other copy of child has already been
- // dropped
- std::sync::Arc::get_mut(&mut dropped_window.child)
- .unwrap()
- .status()
- .await
- .unwrap();
- if state.windows.is_empty() {
- break;
- }
- state.notify(ex, &format!("window {} exited", id));
-
- state.redraw_current_window(&mut tm).await;
- }
- Ok(Event::Command(c)) => match c {
- Command::NewWindow => {
- state.new_window(ex, state.wevents.clone());
- state.redraw_current_window(&mut tm).await;
- }
- Command::NextWindow => {
- state.next_window(ex);
- state.redraw_current_window(&mut tm).await;
- }
- },
- Ok(Event::Notification) => {
- state.update_current_window(&mut tm).await;
- }
- Err(e) => {
- eprintln!("{}", e);
- break;
- }
- }
- }
- })
- .await;
- }
-}
+#[cfg(feature = "async")]
+mod tmux_impl;
+#[cfg(feature = "async")]
async fn async_main(ex: &smol::Executor<'_>) {
- let tmux = Tmux::new().await;
+ let tmux = tmux_impl::Tmux::new().await;
tmux.run(ex).await;
}
+#[cfg(feature = "async")]
fn main() {
let ex = smol::Executor::new();
smol::block_on(async { async_main(&ex).await })
}
+
+#[cfg(not(feature = "async"))]
+fn main() {
+ panic!("tmux example requires feature async")
+}
diff --git a/examples/tmux_impl/mod.rs b/examples/tmux_impl/mod.rs
new file mode 100644
index 0000000..de026e4
--- /dev/null
+++ b/examples/tmux_impl/mod.rs
@@ -0,0 +1,379 @@
+use pty_process::Command as _;
+use smol::io::{AsyncReadExt as _, AsyncWriteExt as _};
+use textmode::Textmode as _;
+
+enum Command {
+ NewWindow,
+ NextWindow,
+}
+
+enum Event {
+ Input(textmode::Key),
+ Output,
+ WindowExit(usize),
+ Command(Command),
+ Notification,
+}
+
+struct Window {
+ child: std::sync::Arc<pty_process::smol::Child>,
+ vt: std::sync::Arc<smol::lock::Mutex<vt100::Parser>>,
+ screen: vt100::Screen,
+}
+
+#[derive(Clone)]
+struct Notification {
+ text: String,
+ expiry: std::time::Instant,
+}
+
+struct State {
+ windows: std::collections::BTreeMap<usize, Window>,
+ current_window: usize,
+ next_window_id: usize,
+ notifications: std::collections::BTreeMap<usize, Notification>,
+ next_notification_id: usize,
+ wevents: smol::channel::Sender<Event>,
+ revents: smol::channel::Receiver<Event>,
+}
+
+impl State {
+ fn new() -> Self {
+ let (sender, receiver) = smol::channel::unbounded();
+ Self {
+ windows: std::collections::BTreeMap::new(),
+ current_window: 0,
+ next_window_id: 0,
+ notifications: std::collections::BTreeMap::new(),
+ next_notification_id: 0,
+ wevents: sender,
+ revents: receiver,
+ }
+ }
+
+ fn current_window(&self) -> &Window {
+ &self.windows[&self.current_window]
+ }
+
+ fn current_window_mut(&mut self) -> &mut Window {
+ self.windows.get_mut(&self.current_window).unwrap()
+ }
+
+ fn next_window(&mut self, ex: &smol::Executor<'_>) {
+ self.current_window = self
+ .windows
+ .keys()
+ .copied()
+ .cycle()
+ .skip_while(|&id| id < self.current_window)
+ .nth(1)
+ .unwrap();
+ self.notify(
+ ex,
+ &format!("switched to window {}", self.current_window),
+ );
+ }
+
+ fn notify(&mut self, ex: &smol::Executor<'_>, text: &str) {
+ let now = std::time::Instant::now();
+ let expiry = now + std::time::Duration::from_secs(5);
+ let text = text.to_string();
+ let notification = Notification { text, expiry };
+ let id = self.next_notification_id;
+ self.next_notification_id += 1;
+ self.notifications.insert(id, notification);
+ let notify = self.wevents.clone();
+ ex.spawn(async move {
+ smol::Timer::at(expiry).await;
+ notify.send(Event::Notification).await.unwrap();
+ })
+ .detach();
+ }
+
+ fn spawn_input_task(
+ &self,
+ ex: &smol::Executor<'_>,
+ mut input: textmode::Input,
+ ) {
+ let notify = self.wevents.clone();
+ ex.spawn(async move {
+ let mut waiting_for_command = false;
+ input.parse_utf8(false);
+ input.parse_meta(false);
+ input.parse_special_keys(false);
+ loop {
+ input.parse_single(waiting_for_command);
+ match input.read_key().await {
+ Ok(Some(key)) => {
+ if waiting_for_command {
+ waiting_for_command = false;
+ match key {
+ textmode::Key::Ctrl(b'n') => {
+ notify
+ .send(Event::Input(key))
+ .await
+ .unwrap();
+ }
+ textmode::Key::Byte(b'c') => {
+ notify
+ .send(Event::Command(
+ Command::NewWindow,
+ ))
+ .await
+ .unwrap();
+ }
+ textmode::Key::Byte(b'n') => {
+ notify
+ .send(Event::Command(
+ Command::NextWindow,
+ ))
+ .await
+ .unwrap();
+ }
+ _ => {} // ignore
+ }
+ } else {
+ match key {
+ textmode::Key::Ctrl(b'n') => {
+ waiting_for_command = true;
+ }
+ _ => {
+ notify
+ .send(Event::Input(key))
+ .await
+ .unwrap();
+ }
+ }
+ }
+ }
+ Ok(None) => {
+ break;
+ }
+ Err(e) => {
+ eprintln!("{}", e);
+ break;
+ }
+ }
+ }
+ })
+ .detach();
+ }
+
+ fn new_window(
+ &mut self,
+ ex: &smol::Executor<'_>,
+ notify: smol::channel::Sender<Event>,
+ ) {
+ let child = smol::process::Command::new("zsh")
+ .spawn_pty(Some(&pty_process::Size::new(24, 80)))
+ .unwrap();
+ let child = std::sync::Arc::new(child);
+ let vt = vt100::Parser::default();
+ let screen = vt.screen().clone();
+ let vt = std::sync::Arc::new(smol::lock::Mutex::new(vt));
+ let id = self.next_window_id;
+ self.next_window_id += 1;
+ let window = Window {
+ child: child.clone(),
+ vt: vt.clone(),
+ screen,
+ };
+ self.windows.insert(id, window);
+ self.current_window = id;
+ self.notify(ex, &format!("created window {}", id));
+ ex.spawn(async move {
+ let mut buf = [0_u8; 4096];
+ loop {
+ match child.pty().read(&mut buf).await {
+ Ok(bytes) => {
+ vt.lock_arc().await.process(&buf[..bytes]);
+ notify.send(Event::Output).await.unwrap();
+ }
+ Err(e) => {
+ // EIO means that the process closed the other
+ // end of the pty
+ if e.raw_os_error() != Some(libc::EIO) {
+ eprintln!("pty read failed: {:?}", e);
+ }
+ notify.send(Event::WindowExit(id)).await.unwrap();
+ break;
+ }
+ }
+ }
+ })
+ .detach();
+ }
+
+ async fn redraw_current_window(&mut self, tm: &mut textmode::Output) {
+ let window = self.current_window();
+ tm.clear();
+ let new_screen = window.vt.lock_arc().await.screen().clone();
+ tm.write(&new_screen.state_formatted());
+ self.draw_notifications(tm, &new_screen);
+ tm.refresh().await.unwrap();
+ }
+
+ async fn update_current_window(&mut self, tm: &mut textmode::Output) {
+ let window = self.current_window();
+ let old_screen = window.screen.clone();
+ let new_screen = window.vt.lock_arc().await.screen().clone();
+ let diff = new_screen.state_diff(&old_screen);
+ self.clear_notifications(tm, &old_screen);
+ tm.write(&diff);
+ self.draw_notifications(tm, &new_screen);
+ tm.refresh().await.unwrap();
+ self.current_window_mut().screen = new_screen;
+ }
+
+ fn clear_notifications(
+ &mut self,
+ tm: &mut textmode::Output,
+ screen: &vt100::Screen,
+ ) {
+ if self.notifications.is_empty() {
+ return;
+ }
+
+ let reset_attrs = screen.attributes_formatted();
+ let pos = screen.cursor_position();
+ for (i, row) in screen
+ .rows_formatted(0, 80)
+ .enumerate()
+ .take(self.notifications.len())
+ {
+ tm.move_to(i as u16, 0);
+ tm.reset_attributes();
+ tm.clear_line();
+ tm.write(&row);
+ }
+ tm.move_to(pos.0, pos.1);
+ tm.write(&reset_attrs);
+ }
+
+ fn draw_notifications(
+ &mut self,
+ tm: &mut textmode::Output,
+ screen: &vt100::Screen,
+ ) {
+ if self.notifications.is_empty() {
+ return;
+ }
+
+ let now = std::time::Instant::now();
+ self.notifications = self
+ .notifications
+ .iter()
+ .map(|(k, v)| (*k, v.clone()))
+ .filter(|(_, v)| v.expiry >= now)
+ .collect();
+
+ if self.notifications.is_empty() {
+ return;
+ }
+
+ let reset_attrs = screen.attributes_formatted();
+ let pos = screen.cursor_position();
+ tm.reset_attributes();
+ tm.set_bgcolor(textmode::color::CYAN);
+ tm.set_fgcolor(textmode::color::WHITE);
+ for (i, notification) in self.notifications.values().enumerate() {
+ tm.move_to(i as u16, 0);
+ tm.clear_line();
+ let str_len = notification.text.len();
+ let spaces = 80 - str_len;
+ let prefix_spaces = spaces / 2;
+ tm.write(&vec![b' '; prefix_spaces]);
+ tm.write_str(&notification.text);
+ }
+ tm.move_to(pos.0, pos.1);
+ tm.write(&reset_attrs);
+ }
+}
+
+#[must_use]
+pub struct Tmux {
+ input: textmode::Input,
+ tm: textmode::Output,
+ state: State,
+}
+
+impl Tmux {
+ pub async fn new() -> Self {
+ let input = textmode::Input::new().await.unwrap();
+ let tm = textmode::Output::new().await.unwrap();
+ let state = State::new();
+ Self { input, tm, state }
+ }
+
+ pub async fn run(self, ex: &smol::Executor<'_>) {
+ let Self {
+ input,
+ mut tm,
+ mut state,
+ } = self;
+
+ state.spawn_input_task(ex, input);
+
+ ex.run(async {
+ state.new_window(ex, state.wevents.clone());
+
+ loop {
+ match state.revents.recv().await {
+ Ok(Event::Output) => {
+ state.update_current_window(&mut tm).await;
+ }
+ Ok(Event::Input(key)) => {
+ state
+ .current_window()
+ .child
+ .pty()
+ .write_all(&key.into_bytes())
+ .await
+ .unwrap();
+ }
+ Ok(Event::WindowExit(id)) => {
+ // do this first because next_window breaks if
+ // current_window is greater than all existing windows
+ if state.current_window == id {
+ state.next_window(ex)
+ }
+ let mut dropped_window =
+ state.windows.remove(&id).unwrap();
+ // i can get_mut because at this point the future
+ // holding the other copy of child has already been
+ // dropped
+ std::sync::Arc::get_mut(&mut dropped_window.child)
+ .unwrap()
+ .status()
+ .await
+ .unwrap();
+ if state.windows.is_empty() {
+ break;
+ }
+ state.notify(ex, &format!("window {} exited", id));
+
+ state.redraw_current_window(&mut tm).await;
+ }
+ Ok(Event::Command(c)) => match c {
+ Command::NewWindow => {
+ state.new_window(ex, state.wevents.clone());
+ state.redraw_current_window(&mut tm).await;
+ }
+ Command::NextWindow => {
+ state.next_window(ex);
+ state.redraw_current_window(&mut tm).await;
+ }
+ },
+ Ok(Event::Notification) => {
+ state.update_current_window(&mut tm).await;
+ }
+ Err(e) => {
+ eprintln!("{}", e);
+ break;
+ }
+ }
+ }
+ })
+ .await;
+ }
+}
diff --git a/tests/basic.rs b/tests/basic.rs
index c18477b..e0856fb 100644
--- a/tests/basic.rs
+++ b/tests/basic.rs
@@ -25,7 +25,7 @@ fn test_basic() {
#[test]
fn test_async() {
- let mut fixture = fixtures::Fixture::new("async");
+ let mut fixture = fixtures::Fixture::new("basic");
fixture.features("async");
fixture.build().run(&[], |pty| {
pty.write_all(b"a").unwrap();
diff --git a/tests/fixtures/bin/src/bin/async.rs b/tests/fixtures/bin/src/bin/async.rs
deleted file mode 120000
index ff890ad..0000000
--- a/tests/fixtures/bin/src/bin/async.rs
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../examples/async.rs \ No newline at end of file