upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/test_utils
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2023-10-01 00:00:00 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2023-10-01 00:00:00 +0100
commit6e9245542f070c39a1975f0d53d88913c4ac667d (patch)
tree835c1d3db05f76f437c5d8ebc5591f1796cdab60 /test_utils
parentaa48a626c08cec353d5563a8831239d2e69c9f3d (diff)
feat(prs-create) find commits and create events
- identify commits - create pull request event - create patch events
Diffstat (limited to 'test_utils')
-rw-r--r--test_utils/Cargo.toml2
-rw-r--r--test_utils/src/git.rs123
-rw-r--r--test_utils/src/lib.rs130
3 files changed, 254 insertions, 1 deletions
diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml
index 1a39957..3d936b5 100644
--- a/test_utils/Cargo.toml
+++ b/test_utils/Cargo.toml
@@ -8,7 +8,9 @@ anyhow = "1.0.75"
8assert_cmd = "2.0.12" 8assert_cmd = "2.0.12"
9dialoguer = "0.10.4" 9dialoguer = "0.10.4"
10directories = "5.0.1" 10directories = "5.0.1"
11git2 = "0.18.1"
11nostr = "0.23.0" 12nostr = "0.23.0"
12once_cell = "1.18.0" 13once_cell = "1.18.0"
14rand = "0.8"
13rexpect = { git = "https://github.com/phaer/rexpect.git", branch= "skip-ansi-escape-codes" } 15rexpect = { git = "https://github.com/phaer/rexpect.git", branch= "skip-ansi-escape-codes" }
14strip-ansi-escapes = "0.2.0" 16strip-ansi-escapes = "0.2.0"
diff --git a/test_utils/src/git.rs b/test_utils/src/git.rs
new file mode 100644
index 0000000..166693d
--- /dev/null
+++ b/test_utils/src/git.rs
@@ -0,0 +1,123 @@
1//create
2
3// implement drop?
4use std::{env::current_dir, fs, path::PathBuf};
5
6use anyhow::Result;
7use git2::{Oid, RepositoryInitOptions, Signature, Time};
8
9pub struct GitTestRepo {
10 pub dir: PathBuf,
11 pub git_repo: git2::Repository,
12}
13
14impl Default for GitTestRepo {
15 fn default() -> Self {
16 Self::new("main").unwrap()
17 }
18}
19impl GitTestRepo {
20 pub fn new(main_branch_name: &str) -> Result<Self> {
21 let path = current_dir()?.join(format!("tmpgit-{}", rand::random::<u64>()));
22 let git_repo = git2::Repository::init_opts(
23 &path,
24 RepositoryInitOptions::new()
25 .initial_head(main_branch_name)
26 .mkpath(true),
27 )?;
28 Ok(Self {
29 dir: path,
30 git_repo,
31 })
32 }
33
34 pub fn initial_commit(&self) -> Result<Oid> {
35 let oid = self.git_repo.index()?.write_tree()?;
36 let tree = self.git_repo.find_tree(oid)?;
37 let commit_oid = self.git_repo.commit(
38 Some("HEAD"),
39 &joe_signature(),
40 &joe_signature(),
41 "Initial commit",
42 &tree,
43 &[],
44 )?;
45 Ok(commit_oid)
46 }
47
48 pub fn populate(&self) -> Result<Oid> {
49 self.initial_commit()?;
50 fs::write(self.dir.join("t1.md"), "some content")?;
51 self.stage_and_commit("add t1.md")?;
52 fs::write(self.dir.join("t2.md"), "some content1")?;
53 self.stage_and_commit("add t2.md")
54 }
55
56 pub fn stage_and_commit(&self, message: &str) -> Result<Oid> {
57 let prev_oid = self.git_repo.head().unwrap().peel_to_commit()?;
58
59 let mut index = self.git_repo.index()?;
60 index.add_all(["."], git2::IndexAddOption::DEFAULT, None)?;
61 index.write()?;
62
63 let oid = self.git_repo.commit(
64 Some("HEAD"),
65 &joe_signature(),
66 &joe_signature(),
67 message,
68 &self.git_repo.find_tree(index.write_tree()?)?,
69 &[&prev_oid],
70 )?;
71
72 Ok(oid)
73 }
74
75 pub fn create_branch(&self, branch_name: &str) -> Result<()> {
76 self.git_repo
77 .branch(branch_name, &self.git_repo.head()?.peel_to_commit()?, false)?;
78 Ok(())
79 }
80
81 pub fn checkout(&self, ref_name: &str) -> Result<Oid> {
82 let (object, reference) = self.git_repo.revparse_ext(ref_name)?;
83
84 self.git_repo.checkout_tree(&object, None)?;
85
86 match reference {
87 // gref is an actual reference like branches or tags
88 Some(gref) => self.git_repo.set_head(gref.name().unwrap()),
89 // this is a commit, not a reference
90 None => self.git_repo.set_head_detached(object.id()),
91 }?;
92 let oid = self.git_repo.head()?.peel_to_commit()?.id();
93 Ok(oid)
94 }
95}
96
97impl Drop for GitTestRepo {
98 fn drop(&mut self) {
99 let _ = fs::remove_dir_all(&self.dir);
100 }
101}
102pub fn joe_signature() -> Signature<'static> {
103 Signature::new("Joe Bloggs", "joe.bloggs@pm.me", &Time::new(0, 0)).unwrap()
104}
105
106#[cfg(test)]
107mod tests {
108
109 use super::*;
110
111 #[test]
112 fn methods_do_not_throw() -> Result<()> {
113 let repo = GitTestRepo::new("main")?;
114
115 repo.populate()?;
116 repo.create_branch("feature")?;
117 repo.checkout("feature")?;
118 fs::write(repo.dir.join("t3.md"), "some content")?;
119 repo.stage_and_commit("add t3.md")?;
120
121 Ok(())
122 }
123}
diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs
index 1a4231a..0f870f6 100644
--- a/test_utils/src/lib.rs
+++ b/test_utils/src/lib.rs
@@ -1,4 +1,4 @@
1use std::ffi::OsStr; 1use std::{ffi::OsStr, path::PathBuf};
2 2
3use anyhow::{ensure, Context, Result}; 3use anyhow::{ensure, Context, Result};
4use dialoguer::theme::{ColorfulTheme, Theme}; 4use dialoguer::theme::{ColorfulTheme, Theme};
@@ -8,6 +8,8 @@ use once_cell::sync::Lazy;
8use rexpect::session::{Options, PtySession}; 8use rexpect::session::{Options, PtySession};
9use strip_ansi_escapes::strip_str; 9use strip_ansi_escapes::strip_str;
10 10
11pub mod git;
12
11pub static TEST_KEY_1_NSEC: &str = 13pub static TEST_KEY_1_NSEC: &str =
12 "nsec1ppsg5sm2aexq06juxmu9evtutr6jkwkhp98exxxvwamhru9lyx9s3rwseq"; 14 "nsec1ppsg5sm2aexq06juxmu9evtutr6jkwkhp98exxxvwamhru9lyx9s3rwseq";
13pub static TEST_KEY_1_SK_HEX: &str = 15pub static TEST_KEY_1_SK_HEX: &str =
@@ -75,6 +77,34 @@ impl CliTester {
75 i.prompt().context("initial password prompt")?; 77 i.prompt().context("initial password prompt")?;
76 Ok(i) 78 Ok(i)
77 } 79 }
80
81 pub fn expect_confirm(
82 &mut self,
83 prompt: &str,
84 default: Option<bool>,
85 ) -> Result<CliTesterConfirmPrompt> {
86 let mut i = CliTesterConfirmPrompt {
87 tester: self,
88 prompt: prompt.to_string(),
89 default,
90 };
91 i.prompt(false, default).context("initial confirm prompt")?;
92 Ok(i)
93 }
94
95 pub fn expect_confirm_eventually(
96 &mut self,
97 prompt: &str,
98 default: Option<bool>,
99 ) -> Result<CliTesterConfirmPrompt> {
100 let mut i = CliTesterConfirmPrompt {
101 tester: self,
102 prompt: prompt.to_string(),
103 default,
104 };
105 i.prompt(true, default).context("initial confirm prompt")?;
106 Ok(i)
107 }
78} 108}
79 109
80pub struct CliTesterInputPrompt<'a> { 110pub struct CliTesterInputPrompt<'a> {
@@ -199,6 +229,71 @@ impl CliTesterPasswordPrompt<'_> {
199 } 229 }
200} 230}
201 231
232pub struct CliTesterConfirmPrompt<'a> {
233 tester: &'a mut CliTester,
234 prompt: String,
235 default: Option<bool>,
236}
237
238impl CliTesterConfirmPrompt<'_> {
239 fn prompt(&mut self, eventually: bool, default: Option<bool>) -> Result<&mut Self> {
240 let mut s = String::new();
241 self.tester
242 .formatter
243 .format_confirm_prompt(&mut s, self.prompt.as_str(), default)
244 .expect("diagluer theme formatter should succeed");
245 ensure!(
246 s.contains(self.prompt.as_str()),
247 "dialoguer must be broken as formatted prompt success doesnt contain prompt"
248 );
249
250 if eventually {
251 self.tester
252 .expect_eventually(sanatize(s).as_str())
253 .context("expect input prompt eventually")?;
254 } else {
255 self.tester
256 .expect(sanatize(s).as_str())
257 .context("expect confirm prompt")?;
258 }
259
260 Ok(self)
261 }
262
263 pub fn succeeds_with(&mut self, input: Option<bool>) -> Result<&mut Self> {
264 self.tester.send_line(match input {
265 None => "",
266 Some(true) => "y",
267 Some(false) => "n",
268 })?;
269 self.tester
270 .expect("\r")
271 .context("expect new line after confirm input to be printed")?;
272
273 let mut s = String::new();
274 self.tester
275 .formatter
276 .format_confirm_prompt_selection(
277 &mut s,
278 self.prompt.as_str(),
279 match input {
280 None => self.default,
281 Some(_) => input,
282 },
283 )
284 .expect("diagluer theme formatter should succeed");
285 if !s.contains(self.prompt.as_str()) {
286 panic!("dialoguer must be broken as formatted prompt success doesnt contain prompt");
287 }
288 let formatted_success = format!("{}\r\n", sanatize(s));
289
290 self.tester
291 .expect(formatted_success.as_str())
292 .context("expect immediate prompt success")?;
293 Ok(self)
294 }
295}
296
202impl CliTester { 297impl CliTester {
203 pub fn new<I, S>(args: I) -> Self 298 pub fn new<I, S>(args: I) -> Self
204 where 299 where
@@ -210,6 +305,17 @@ impl CliTester {
210 formatter: ColorfulTheme::default(), 305 formatter: ColorfulTheme::default(),
211 } 306 }
212 } 307 }
308 pub fn new_from_dir<I, S>(dir: &PathBuf, args: I) -> Self
309 where
310 I: IntoIterator<Item = S>,
311 S: AsRef<OsStr>,
312 {
313 Self {
314 rexpect_session: rexpect_with_from_dir(dir, args, 2000)
315 .expect("rexpect to spawn new process"),
316 formatter: ColorfulTheme::default(),
317 }
318 }
213 pub fn new_with_timeout<I, S>(timeout_ms: u64, args: I) -> Self 319 pub fn new_with_timeout<I, S>(timeout_ms: u64, args: I) -> Self
214 where 320 where
215 I: IntoIterator<Item = S>, 321 I: IntoIterator<Item = S>,
@@ -338,6 +444,28 @@ where
338 ) 444 )
339} 445}
340 446
447pub fn rexpect_with_from_dir<I, S>(
448 dir: &PathBuf,
449 args: I,
450 timeout_ms: u64,
451) -> Result<PtySession, rexpect::error::Error>
452where
453 I: IntoIterator<Item = S>,
454 S: AsRef<std::ffi::OsStr>,
455{
456 let mut cmd = std::process::Command::new(assert_cmd::cargo::cargo_bin("ngit"));
457 cmd.current_dir(dir);
458 cmd.args(args);
459 // using branch for PR https://github.com/rust-cli/rexpect/pull/103 to strip ansi escape codes
460 rexpect::session::spawn_with_options(
461 cmd,
462 Options {
463 timeout_ms: Some(timeout_ms),
464 strip_ansi_escape_codes: true,
465 },
466 )
467}
468
341/// backup and remove application config and data 469/// backup and remove application config and data
342pub fn before() -> Result<()> { 470pub fn before() -> Result<()> {
343 backup_existing_config() 471 backup_existing_config()