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
112
113
114
115
116
117
118
119
120
121
122
123
|
use futures::{Future, FutureExt};
use std::collections::HashMap;
use std::io::BufRead;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::mpsc;
use planetwars_matchrunner::docker_runner::DockerBotSpec;
use planetwars_matchrunner::match_context::{EventBus, MatchCtx, RequestError};
use planetwars_matchrunner::BotSpec;
use planetwars_matchrunner::{run_match, MatchConfig, MatchPlayer};
const PYTHON_IMAGE: &str = "python:3.10-slim-buster";
fn simple_python_docker_bot_spec(source_dir: &str, file_name: &str) -> DockerBotSpec {
let source_dir_path = std::fs::canonicalize(source_dir).unwrap();
let source_dir_path_str = source_dir_path.as_os_str().to_str().unwrap();
DockerBotSpec {
image: PYTHON_IMAGE.to_string(),
binds: Some(vec![format!("{}:{}", source_dir_path_str, "/workdir")]),
argv: Some(vec!["python".to_string(), file_name.to_string()]),
working_dir: Some("/workdir".to_string()),
pull: false,
credentials: None,
}
}
#[tokio::test]
async fn match_does_run() {
let bot = simple_python_docker_bot_spec("./bots/simplebot", "simplebot.py");
let log_file = tempfile::NamedTempFile::new().unwrap();
let config = MatchConfig {
map_name: "hex".to_string(),
map_path: PathBuf::from("maps/abc.json"),
log_path: PathBuf::from(log_file.path()),
players: vec![
MatchPlayer {
bot_spec: Box::new(bot.clone()),
},
MatchPlayer {
bot_spec: Box::new(bot.clone()),
},
],
};
run_match(config).await;
let line_count = std::io::BufReader::new(log_file.as_file()).lines().count();
assert!(line_count > 0);
tokio::time::sleep(Duration::from_secs(1)).await
}
/// creates a simple match ctx which only holds a single bot
async fn with_bot_match_ctx<B, F>(bot_spec: B, func: F)
where
F: FnOnce(&mut MatchCtx) -> Pin<Box<dyn '_ + Future<Output = ()>>>,
B: BotSpec,
{
let event_bus = Arc::new(Mutex::new(EventBus::new()));
let (logger, _rx) = mpsc::unbounded_channel();
let player_handle = bot_spec.run_bot(1, event_bus.clone(), logger.clone()).await;
let mut players = HashMap::new();
players.insert(1, player_handle);
let mut ctx = MatchCtx::new(event_bus, players, logger);
func(&mut ctx).await;
ctx.shutdown().await;
}
#[tokio::test]
async fn docker_runner_success() {
let bot_spec = simple_python_docker_bot_spec("./bots", "echo_bot.py");
with_bot_match_ctx(bot_spec, |ctx| {
async move {
let resp = ctx
.request(1, b"sup".to_vec(), Duration::from_millis(200))
.await;
assert_eq!(resp, Ok(b"sup\n".to_vec()));
}
.boxed()
})
.await;
}
#[tokio::test]
async fn docker_runner_timeout() {
let bot_spec = simple_python_docker_bot_spec("./bots", "timeout_bot.py");
with_bot_match_ctx(bot_spec, |ctx| {
async move {
let resp = ctx
.request(1, b"sup".to_vec(), Duration::from_millis(200))
.await;
assert_eq!(resp, Err(RequestError::Timeout));
}
.boxed()
})
.await;
}
#[tokio::test]
async fn docker_runner_crash() {
let bot_spec = simple_python_docker_bot_spec("./bots", "crash_bot.py");
with_bot_match_ctx(bot_spec, |ctx| {
async move {
let resp = ctx
.request(1, b"sup".to_vec(), Duration::from_millis(200))
.await;
assert_eq!(resp, Err(RequestError::BotTerminated));
}
.boxed()
})
.await;
}
|