Skip to content

Commit ea0cd98

Browse files
committed
Implement comment communication in tests
1 parent 21da78d commit ea0cd98

File tree

6 files changed

+122
-46
lines changed

6 files changed

+122
-46
lines changed

src/bors/handlers/ping.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,13 @@ pub(super) async fn command_ping<Client: RepositoryClient>(
1717

1818
#[cfg(test)]
1919
mod tests {
20-
use tracing_test::traced_test;
21-
22-
use crate::tests::event::default_pr_number;
2320
use crate::tests::mocks::run_test;
24-
use crate::tests::state::ClientBuilder;
2521

2622
#[sqlx::test]
2723
async fn test_ping(pool: sqlx::PgPool) {
28-
let state = ClientBuilder::default()
29-
.pool(pool.clone())
30-
.create_state()
31-
.await;
32-
state.comment("@bors ping").await;
33-
state
34-
.client()
35-
.check_comments(default_pr_number(), &["Pong 🏓!"]);
36-
}
37-
38-
#[traced_test]
39-
#[sqlx::test]
40-
async fn test_ping2(pool: sqlx::PgPool) {
4124
run_test(pool, |mut tester| async {
4225
tester.post_comment("@bors ping").await;
26+
assert_eq!(tester.get_comment().await, "Pong 🏓!");
4327
Ok(tester)
4428
})
4529
.await;

src/tests/mocks/bors.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ use tower::Service;
1010

1111
use crate::github::api::load_repositories;
1212
use crate::tests::database::MockedDBClient;
13+
use crate::tests::event::default_pr_number;
1314
use crate::tests::mocks::comment::{Comment, GitHubIssueCommentEventPayload};
1415
use crate::tests::mocks::webhook::{create_webhook_request, TEST_WEBHOOK_SECRET};
15-
use crate::tests::mocks::{ExternalHttpMock, World};
16+
use crate::tests::mocks::{ExternalHttpMock, Repo, World};
1617
use crate::{
1718
create_app, create_bors_process, BorsContext, CommandParser, ServerState, WebhookSecret,
1819
};
@@ -106,8 +107,22 @@ impl BorsTester {
106107
}
107108
}
108109

110+
/// Wait until the next bot comment is received on the default repo and the default PR.
111+
pub async fn get_comment(&mut self) -> String {
112+
self.http_mock
113+
.gh_server
114+
.get_comment(Repo::default().name, default_pr_number())
115+
.await
116+
.content
117+
}
118+
109119
pub async fn post_comment(&mut self, content: &str) {
110-
self.webhook_comment(Comment::new(content)).await;
120+
self.webhook_comment(Comment::new(
121+
Repo::default().name,
122+
default_pr_number(),
123+
content,
124+
))
125+
.await;
111126
}
112127

113128
async fn webhook_comment(&mut self, comment: Comment) {
@@ -140,7 +155,11 @@ impl BorsTester {
140155
}
141156

142157
pub async fn finish(self) {
158+
// Make sure that the event channel senders are closed
143159
drop(self.app);
160+
// Wait until all events are handled in the bors service
144161
self.bors.await.unwrap();
162+
// Flush any local queues
163+
self.http_mock.gh_server.assert_empty_queues().await;
145164
}
146165
}

src/tests/mocks/comment.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,30 @@ use serde::Serialize;
66
use url::Url;
77

88
use crate::github::GithubRepoName;
9-
use crate::tests::event::default_pr_number;
10-
use crate::tests::mocks::repository::{GitHubRepository, Repo};
9+
use crate::tests::mocks::repository::GitHubRepository;
1110
use crate::tests::mocks::user::{GitHubUser, User};
1211

1312
#[derive(Clone, Debug)]
1413
pub struct Comment {
15-
repo: GithubRepoName,
16-
pr: u64,
17-
author: User,
18-
content: String,
14+
pub repo: GithubRepoName,
15+
pub pr: u64,
16+
pub author: User,
17+
pub content: String,
1918
}
2019

2120
impl Comment {
22-
pub fn new(content: &str) -> Self {
21+
pub fn new(repo: GithubRepoName, pr: u64, content: &str) -> Self {
2322
Self {
24-
repo: Repo::default().name,
25-
pr: default_pr_number(),
23+
repo,
24+
pr,
2625
author: User::default(),
2726
content: content.to_string(),
2827
}
2928
}
29+
30+
pub fn with_author(self, author: User) -> Self {
31+
Self { author, ..self }
32+
}
3033
}
3134

3235
// Copied from octocrab, since its version if #[non_exhaustive]

src/tests/mocks/github.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use octocrab::Octocrab;
22
use std::collections::HashMap;
3+
use std::time::Duration;
34
use wiremock::MockServer;
45

56
use crate::create_github_client;
@@ -9,6 +10,8 @@ use crate::tests::mocks::comment::Comment;
910
use crate::tests::mocks::repository::{mock_repo, mock_repo_list};
1011
use crate::tests::mocks::World;
1112

13+
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
14+
1215
/// Represents the state of a simulated GH repo.
1316
pub struct GitHubRepoState {
1417
// We store comments from all PRs inside a single queue, because
@@ -18,6 +21,35 @@ pub struct GitHubRepoState {
1821
pending_comments: Vec<Comment>,
1922
}
2023

24+
impl GitHubRepoState {
25+
/// Wait until a comment from the given pull request was received.
26+
/// If a comment from a different PR is received, it is inserted into
27+
/// `pending_comments`, to be picked up later.
28+
async fn get_comment(&mut self, pr: u64) -> Comment {
29+
// First, try to resolve the comment from the pending comment list
30+
if let Some(index) = self.pending_comments.iter().position(|c| c.pr == pr) {
31+
return self.pending_comments.remove(index);
32+
}
33+
// If it is not there, wait until some comment is received
34+
loop {
35+
let comment = self
36+
.comments_queue
37+
.recv()
38+
.await
39+
.expect("Channel was closed while waiting for a comment");
40+
41+
if comment.pr == pr {
42+
return comment;
43+
}
44+
tracing::warn!(
45+
"Received comment for PR {}, while expected for PR {pr}",
46+
comment.pr
47+
);
48+
self.pending_comments.push(comment);
49+
}
50+
}
51+
}
52+
2153
pub struct GitHubMockServer {
2254
mock_server: MockServer,
2355
repos: HashMap<GithubRepoName, GitHubRepoState>,
@@ -38,7 +70,7 @@ impl GitHubMockServer {
3870
name.clone(),
3971
GitHubRepoState {
4072
comments_queue: comments_rx,
41-
pending_comments: vec![],
73+
pending_comments: Default::default(),
4274
},
4375
);
4476
}
@@ -56,6 +88,38 @@ impl GitHubMockServer {
5688
)
5789
.unwrap()
5890
}
91+
92+
pub async fn get_comment(&mut self, repo: GithubRepoName, pr: u64) -> Comment {
93+
let repo = self.repos.get_mut(&repo).unwrap();
94+
let fut = repo.get_comment(pr);
95+
tokio::time::timeout(DEFAULT_TIMEOUT, fut)
96+
.await
97+
.expect("Timed out while waiting for a comment to be received")
98+
}
99+
100+
/// Make sure that there are no leftover events left in the queues.
101+
pub async fn assert_empty_queues(mut self) {
102+
// This will remove all mocks and thus also any leftover
103+
// channel senders, so that we can be sure below that the `recv`
104+
// call will not block indefinitely.
105+
self.mock_server.reset().await;
106+
drop(self.mock_server);
107+
108+
for (name, repo) in self.repos.iter_mut() {
109+
if !repo.pending_comments.is_empty() {
110+
panic!(
111+
"Expected that {name} won't have any received comments, but it has {:?}",
112+
repo.pending_comments
113+
);
114+
}
115+
// Make sure that the queue has been closed and nothing is in it.
116+
if let Some(comment) = repo.comments_queue.recv().await {
117+
panic!(
118+
"Expected that {name} won't have any received comments, but it has {comment:?}"
119+
);
120+
}
121+
}
122+
}
59123
}
60124

61125
const GITHUB_MOCK_PRIVATE_KEY: &str = r###"-----BEGIN PRIVATE KEY-----

src/tests/mocks/pull_request.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,46 @@
11
use serde::{Deserialize, Serialize};
2+
use tokio::sync::mpsc::Sender;
23
use wiremock::{
34
matchers::{method, path},
45
Mock, MockServer, Request, ResponseTemplate,
56
};
67

78
use super::{
89
comment::{Comment, GitHubComment},
9-
Repo,
10+
Repo, User,
1011
};
1112

12-
pub async fn mock_pull_requests(repo: &Repo, mock_server: &MockServer) {
13-
for pr_number in &repo.known_prs {
13+
pub async fn mock_pull_requests(
14+
repo: &Repo,
15+
comments_tx: Sender<Comment>,
16+
mock_server: &MockServer,
17+
) {
18+
let repo_name = repo.name.clone();
19+
for &pr_number in &repo.known_prs {
1420
Mock::given(method("GET"))
15-
.and(path(format!("/repos/{}/pulls/{}", repo.name, pr_number)))
21+
.and(path(format!("/repos/{repo_name}/pulls/{pr_number}")))
1622
.respond_with(
17-
ResponseTemplate::new(200).set_body_json(GitHubPullRequest::new(*pr_number)),
23+
ResponseTemplate::new(200).set_body_json(GitHubPullRequest::new(pr_number)),
1824
)
1925
.mount(mock_server)
2026
.await;
2127

28+
let repo_name = repo_name.clone();
29+
let comments_tx = comments_tx.clone();
2230
Mock::given(method("POST"))
2331
.and(path(format!(
24-
"/repos/{}/issues/{}/comments",
25-
repo.name, pr_number
32+
"/repos/{repo_name}/issues/{pr_number}/comments",
2633
)))
2734
.respond_with(move |req: &Request| {
2835
let comment_payload: CommentCreatePayload = req.body_json().unwrap();
29-
let comment: Comment = comment_payload.into();
30-
// let mut comments = comments.lock().unwrap();
31-
// comments.entry(1).or_default().push(comment.clone());
36+
let comment: Comment =
37+
Comment::new(repo_name.clone(), pr_number, &comment_payload.body)
38+
.with_author(User::new(1002, "bors"));
39+
40+
// We cannot use `tx.blocking_send()`, because this function is actually called
41+
// from within an async task, but it is not async, so we also cannot use
42+
// `tx.send()`.
43+
comments_tx.try_send(comment.clone()).unwrap();
3244
ResponseTemplate::new(201).set_body_json(GitHubComment::from(comment))
3345
})
3446
.mount(mock_server)
@@ -101,9 +113,3 @@ fn default_pr_number() -> u64 {
101113
struct CommentCreatePayload {
102114
body: String,
103115
}
104-
105-
impl From<CommentCreatePayload> for Comment {
106-
fn from(payload: CommentCreatePayload) -> Self {
107-
Comment::new(payload.body.as_str())
108-
}
109-
}

src/tests/mocks/repository.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub async fn mock_repo_list(world: &World, mock_server: &MockServer) {
9292
}
9393

9494
pub async fn mock_repo(repo: &Repo, comments_tx: Sender<Comment>, mock_server: &MockServer) {
95-
mock_pull_requests(repo, mock_server).await;
95+
mock_pull_requests(repo, comments_tx, mock_server).await;
9696
mock_config(repo, mock_server).await;
9797
}
9898

0 commit comments

Comments
 (0)