Compare commits

..

2 commits

Author SHA1 Message Date
Renovate Bot
23e3e29e56 fix(deps): update rust crate kxio to v2
Some checks failed
Test / checks (map[name:nightly]) (push) Failing after 25s
Test / checks (map[name:stable]) (push) Failing after 1m27s
2024-11-15 17:13:44 +00:00
33d49ce712 build: add build steps as default recipe in justfile
All checks were successful
Test / checks (map[name:nightly]) (push) Successful in 4m54s
Test / checks (map[name:stable]) (push) Successful in 4m35s
2024-11-14 20:32:46 +00:00
14 changed files with 223 additions and 191 deletions

2
.gitignore vendored
View file

@ -25,3 +25,5 @@ Cargo.lock
# Added by cargo
/target
/mutants.out/
/mutants.out.old/

View file

@ -9,7 +9,10 @@ anyhow = "1.0"
bon = "3.0"
ignore = "0.4"
file-format = { version = "0.26", features = ["reader-txt"] }
kxio = "1.2"
kxio = "2.1"
regex = "1.10"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.37", features = ["full"] }

View file

@ -1,3 +1,15 @@
build:
#!/usr/bin/env bash
set -e
cargo fmt
cargo fmt --check
cargo hack clippy
cargo hack build
cargo hack test
cargo doc
# cargo test --example get
cargo mutants --jobs 4
self-test:
just test $PWD forgejo-todo-checker

View file

@ -3,17 +3,16 @@ use crate::model::Config;
use crate::patterns::issue_pattern;
use crate::printer::Printer;
use anyhow::{Context, Result};
use kxio::fs;
use kxio::network::Network;
use kxio::{fs::FileSystem, net::Net};
pub fn init_config(printer: &impl Printer, net: Network) -> Result<Config> {
pub fn init_config<'net, 'fs>(
printer: &impl Printer,
fs: &'fs FileSystem,
net: &'net Net,
) -> Result<Config<'net, 'fs>> {
let config = Config::builder()
.net(net)
.fs(fs::new(
std::env::var("GITHUB_WORKSPACE")
.context("GITHUB_WORKSPACE")?
.into(),
))
.fs(fs)
.repo(std::env::var("GITHUB_REPOSITORY").context("GITHUB_REPOSITORY")?)
.server(std::env::var("GITHUB_SERVER_URL").context("GITHUB_SERVER_URL")?)
.issue_pattern(issue_pattern()?)

View file

@ -1,33 +1,28 @@
//
use std::collections::HashSet;
//
use crate::model::Config;
use anyhow::Result;
use kxio::network::{NetRequest, NetUrl};
use super::Issue;
pub async fn fetch_open_issues(config: &Config) -> Result<HashSet<Issue>> {
pub async fn fetch_open_issues<'net, 'fs>(config: &Config<'net, 'fs>) -> Result<HashSet<Issue>> {
let server_url = config.server();
let repo = config.repo();
let url = format!("{server_url}/api/v1/repos/{repo}/issues?state=open");
let request_builder = NetRequest::get(NetUrl::new(url));
let net = config.net();
let client = net.client();
let request = client.get(url);
let request = if let Some(auth_token) = config.auth_token() {
request_builder.header("Authorization", auth_token)
request.header("Authorization", auth_token)
} else {
request_builder
}
.build();
request
};
// .build();
let issues: HashSet<Issue> = config
.net()
.get::<Vec<Issue>>(request)
.await? // tarpaulin uncovered okay
.response_body()
.unwrap_or_default()
.into_iter()
.collect();
let response = net.send(request).await?;
let issues: HashSet<Issue> = response.json().await?;
Ok(issues)
}

View file

@ -1,3 +1,6 @@
//
use super::*;
use std::collections::HashSet;
use crate::tests::a_config;
@ -7,26 +10,39 @@ use super::*;
use anyhow::Result;
use kxio::network::StatusCode;
use std::collections::HashSet;
use crate::tests::a_config;
use kxio::net::{Method, Net, Url};
#[tokio::test]
async fn fetch_lists_issues() -> Result<()> {
async fn fetch_lists_issues() {
//given
let mut net = kxio::network::MockNetwork::new();
net.add_get_response(
"https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open",
StatusCode::OK,
r#"[{"number":13},{"number":64}]"#,
let mock_net = kxio::net::mock();
mock_net
.on(Method::GET)
.url(
Url::parse("https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open")
.expect("parse url"),
)
.respond(
mock_net
.response()
.status(200)
.body(r#"[{"number":13},{"number":64}]"#)
.expect("response body"),
);
let config = a_config(net.into(), kxio::fs::temp()?)?;
let fs = kxio::fs::temp().expect("temp fs");
let net = Net::from(mock_net);
let config = a_config(&net, &fs).expect("config");
//when
let result = fetch_open_issues(&config).await?;
let result = fetch_open_issues(&config).await.expect("when");
//then
assert_eq!(
result,
HashSet::from_iter(vec![Issue::new(13), Issue::new(64)])
);
Ok(())
}

View file

@ -1,9 +1,8 @@
//
use anyhow::{bail, Result};
use anyhow::{bail, Context as _, Result};
use init::init_config;
use issues::fetch_open_issues;
use kxio::network::Network;
use printer::Printer;
use printer::{Printer, StandardPrinter};
use scanner::{find_markers, DefaultFileScanner};
mod init;
@ -19,15 +18,23 @@ mod tests;
#[tokio::main]
#[cfg(not(tarpaulin_include))]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
use printer::StandardPrinter;
Ok(run(&StandardPrinter, Network::new_real()).await?)
let github_workspace = std::env::var("GITHUB_WORKSPACE").context("GITHUB_WORKSPACE")?;
let fs = kxio::fs::new(github_workspace);
let net = kxio::net::new();
Ok(run(&StandardPrinter, &fs, &net).await?)
}
async fn run(printer: &impl Printer, net: Network) -> Result<()> {
async fn run(
printer: &impl Printer,
fs: &kxio::fs::FileSystem,
net: &kxio::net::Net,
) -> Result<()> {
printer.println("Forgejo TODO Checker!");
let config = init_config(printer, net)?;
let config = init_config(printer, fs, net)?;
let issues = fetch_open_issues(&config).await?;
let errors = find_markers(printer, &config, issues, &DefaultFileScanner)?;

View file

@ -2,21 +2,21 @@
use bon::Builder;
use regex::Regex;
#[derive(Debug, Builder)]
pub struct Config {
net: kxio::network::Network,
fs: kxio::fs::FileSystem,
#[derive(Builder)]
pub struct Config<'net, 'fs> {
net: &'net kxio::net::Net,
fs: &'fs kxio::fs::FileSystem,
repo: String,
server: String,
auth_token: Option<String>,
issue_pattern: Regex,
}
impl Config {
pub fn net(&self) -> &kxio::network::Network {
&self.net
impl<'net, 'fs> Config<'net, 'fs> {
pub fn net(&self) -> &kxio::net::Net {
self.net
}
pub fn fs(&self) -> &kxio::fs::FileSystem {
&self.fs
self.fs
}
pub fn repo(&self) -> &str {
&self.repo

View file

@ -6,9 +6,9 @@ use crate::{patterns::issue_pattern, tests::a_config};
#[tokio::test]
async fn with_config_get_net() -> Result<()> {
//given
let net = kxio::network::Network::new_mock();
let net = kxio::net::mock().into();
let fs = kxio::fs::temp()?;
let config = a_config(net, fs)?;
let config = a_config(&net, &fs)?;
//when
config.net();
@ -21,9 +21,9 @@ async fn with_config_get_net() -> Result<()> {
#[test]
fn with_config_get_fs() -> Result<()> {
//given
let net = kxio::network::Network::new_mock();
let net = kxio::net::mock().into();
let fs = kxio::fs::temp()?;
let config = a_config(net, fs.clone())?;
let config = a_config(&net, &fs)?;
//when
let result = config.fs();
@ -37,9 +37,9 @@ fn with_config_get_fs() -> Result<()> {
#[test]
fn with_config_get_issue_pattern() -> Result<()> {
//given
let net = kxio::network::Network::new_mock();
let net = kxio::net::mock().into();
let fs = kxio::fs::temp()?;
let config = a_config(net, fs)?;
let config = a_config(&net, &fs)?;
//when
let result = config.issue_pattern();
@ -53,9 +53,9 @@ fn with_config_get_issue_pattern() -> Result<()> {
#[test]
fn with_config_get_server() -> Result<()> {
//given
let net = kxio::network::Network::new_mock();
let net = kxio::net::mock().into();
let fs = kxio::fs::temp()?;
let config = a_config(net, fs)?;
let config = a_config(&net, &fs)?;
//when
let result = config.server();
@ -69,9 +69,9 @@ fn with_config_get_server() -> Result<()> {
#[test]
fn with_config_get_auth_token() -> Result<()> {
//given
let net = kxio::network::Network::new_mock();
let net = kxio::net::mock().into();
let fs = kxio::fs::temp()?;
let config = a_config(net, fs)?;
let config = a_config(&net, &fs)?;
//when
let result = config.auth_token();
@ -85,9 +85,9 @@ fn with_config_get_auth_token() -> Result<()> {
#[test]
fn with_config_get_repo() -> Result<()> {
//given
let net = kxio::network::Network::new_mock();
let net = kxio::net::mock().into();
let fs = kxio::fs::temp()?;
let config = a_config(net, fs)?;
let config = a_config(&net, &fs)?;
//when
let result = config.repo();

View file

@ -6,7 +6,7 @@ use crate::{
model::{Config, Line, Marker},
printer::Printer,
};
use anyhow::Result;
use anyhow::{Context as _, Result};
use file_format::FileFormat;
use ignore::Walk;
@ -29,15 +29,17 @@ pub fn find_markers(
let mut errors = 0;
for file in Walk::new(config.fs().base()).flatten() {
let path = file.path();
if is_text_file(config, path)? {
errors += file_scanner.scan_file(path, config, printer, &issues)?
if is_text_file(config, path).context("is text file")? {
errors += file_scanner
.scan_file(path, config, printer, &issues)
.context("scan file")?
}
}
Ok(errors)
}
fn is_text_file(config: &Config, path: &Path) -> Result<bool> {
Ok(config.fs().path_is_file(path)?
Ok(config.fs().path(path).is_file()?
&& FileFormat::from_file(path)?
.media_type()
.starts_with("text/"))
@ -56,7 +58,9 @@ impl FileScanner for DefaultFileScanner {
let mut errors = 0;
config
.fs()
.file_read_to_string(file)? // tarpaulin uncovered okay
.file(file)
.reader()?
.to_string() // tarpaulin uncovered okay
.lines()
.enumerate()
.map(|(n, line)| {

View file

@ -2,32 +2,37 @@
use super::*;
use assert2::let_assert;
use kxio::network::Network;
use kxio::net::Net;
use model::Config;
use patterns::issue_pattern;
use printer::TestPrinter;
#[test]
fn init_when_all_valid() -> anyhow::Result<()> {
fn init_when_all_valid() {
//given
let fs = kxio::fs::temp().expect("temp fs");
let _env = THE_ENVIRONMENT.lock();
let fs = kxio::fs::temp()?;
std::env::set_var("GITHUB_WORKSPACE", fs.base());
std::env::set_var("GITHUB_REPOSITORY", "repo");
std::env::set_var("GITHUB_SERVER_URL", "server");
let net = Network::new_mock();
let mock_net = kxio::net::mock();
let net = Net::from(mock_net);
let printer = TestPrinter::default();
let expected = Config::builder()
.net(net.clone())
.fs(kxio::fs::new(fs.base().to_path_buf()))
.net(&net)
.fs(&fs)
.repo("repo".to_string())
.server("server".to_string())
.issue_pattern(issue_pattern()?)
.issue_pattern(issue_pattern().expect("pattern"))
.maybe_auth_token(Some("auth".to_string()))
.build();
//when
let result = init_config(&printer, net)?;
let result = init_config(&printer, &fs, &net).expect("config");
//then
assert_eq!(result.fs().base(), expected.fs().base());
@ -37,65 +42,50 @@ fn init_when_all_valid() -> anyhow::Result<()> {
result.issue_pattern().to_string(),
expected.issue_pattern().to_string()
);
Ok(())
}
#[test]
fn init_when_no_workspace() -> anyhow::Result<()> {
fn init_when_no_repository() {
//given
let fs = kxio::fs::temp().expect("temp fs");
let _env = THE_ENVIRONMENT.lock();
std::env::remove_var("GITHUB_WORKSPACE");
std::env::set_var("GITHUB_REPOSITORY", "repo");
std::env::set_var("GITHUB_SERVER_URL", "server");
let printer = TestPrinter::default();
//when
let result = init_config(&printer, Network::new_mock());
//then
let_assert!(Err(e) = result);
assert_eq!(e.to_string(), "GITHUB_WORKSPACE");
Ok(())
}
#[test]
fn init_when_no_repository() -> anyhow::Result<()> {
//given
let _env = THE_ENVIRONMENT.lock();
let fs = kxio::fs::temp()?;
std::env::set_var("GITHUB_WORKSPACE", fs.base());
std::env::remove_var("GITHUB_REPOSITORY");
std::env::set_var("GITHUB_SERVER_URL", "server");
let printer = TestPrinter::default();
let mock_net = kxio::net::mock();
let net = Net::from(mock_net);
//when
let result = init_config(&printer, Network::new_mock());
let result = init_config(&printer, &fs, &net);
//then
let_assert!(Err(e) = result);
assert_eq!(e.to_string(), "GITHUB_REPOSITORY");
Ok(())
}
#[test]
fn init_when_no_server_url() -> anyhow::Result<()> {
fn init_when_no_server_url() {
//given
let fs = kxio::fs::temp().expect("temp fs");
let _env = THE_ENVIRONMENT.lock();
let fs = kxio::fs::temp()?;
std::env::set_var("GITHUB_WORKSPACE", fs.base());
std::env::set_var("GITHUB_REPOSITORY", "repo");
std::env::remove_var("GITHUB_SERVER_URL");
let printer = TestPrinter::default();
let mock_net = kxio::net::mock();
let net = Net::from(mock_net);
//when
let result = init_config(&printer, Network::new_mock());
let result = init_config(&printer, &fs, &net);
//then
let_assert!(Err(e) = result);
assert_eq!(e.to_string(), "GITHUB_SERVER_URL");
Ok(())
}

View file

@ -13,7 +13,10 @@ mod scanner;
pub static THE_ENVIRONMENT: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
pub fn a_config(net: kxio::network::Network, fs: kxio::fs::FileSystem) -> Result<Config> {
pub fn a_config<'net, 'fs>(
net: &'net kxio::net::Net,
fs: &'fs kxio::fs::FileSystem,
) -> Result<Config<'net, 'fs>> {
Ok(Config::builder()
.net(net)
.fs(fs)

View file

@ -2,84 +2,83 @@
use super::*;
use anyhow::Result;
use kxio::network::{RequestBody, RequestMethod, SavedRequest, StatusCode};
use pretty_assertions::assert_eq;
use kxio::net::{Method, Net, Url};
use printer::TestPrinter;
use url::Url;
#[tokio::test]
async fn run_with_some_invalids() -> Result<()> {
async fn run_with_some_invalids() /* -> Result<()> */
{
//given
let mut net = kxio::network::MockNetwork::new();
net.add_get_response(
"https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open",
StatusCode::OK,
r#"[{"number": 13}]"#,
let mock_net = kxio::net::mock();
mock_net
.on(Method::GET)
.url(
Url::parse("https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open")
.expect("parse url"),
)
.respond(
mock_net
.response()
.status(200)
.body(r#"[{"number": 13}]"#)
.expect("response body"),
);
let net = Net::from(mock_net);
let fs = kxio::fs::temp().expect("temp fs");
fs.file(&fs.base().join("file_with_invalids.txt"))
.write(include_str!("data/file_with_invalids.txt"))
.expect("write file with invalids");
fs.file(&fs.base().join("file_with_valids.txt"))
.write(include_str!("data/file_with_valids.txt"))
.expect("write file with valids");
let _env = THE_ENVIRONMENT.lock();
let fs = kxio::fs::temp()?;
fs.file_write(
&fs.base().join("file_with_invalids.txt"),
include_str!("data/file_with_invalids.txt"),
)?;
fs.file_write(
&fs.base().join("file_with_valids.txt"),
include_str!("data/file_with_valids.txt"),
)?;
std::env::set_var("GITHUB_WORKSPACE", fs.base());
std::env::set_var("GITHUB_REPOSITORY", "kemitix/test");
std::env::set_var("GITHUB_SERVER_URL", "https://git.kemitix.net");
//when
let result = run(&TestPrinter::default(), net.clone().into()).await;
let result = run(&TestPrinter::default(), &fs, &net).await;
//then
assert!(result.is_err()); // there is an invalid file
let requests = net.requests();
assert_eq!(
requests,
vec![SavedRequest::new(
RequestMethod::Get,
"https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open",
RequestBody::None,
)]
);
Ok(())
}
#[tokio::test]
async fn run_with_no_invalids() -> Result<()> {
async fn run_with_no_invalids() {
//given
let mut net = kxio::network::MockNetwork::new();
net.add_get_response(
"https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open",
StatusCode::OK,
r#"[{"number":23},{"number":43}]"#,
let mock_net = kxio::net::mock();
mock_net
.on(Method::GET)
.url(
Url::parse("https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open")
.expect("parse url"),
)
.respond(
mock_net
.response()
.status(200)
.body(r#"[{"number":23},{"number":43}]"#)
.expect("response body"),
);
let net = Net::from(mock_net);
let fs = kxio::fs::temp().expect("temp fs");
fs.file(&fs.base().join("file_with_valids.txt"))
.write(include_str!("data/file_with_valids.txt"))
.expect("write file with valids");
let _env = THE_ENVIRONMENT.lock();
let fs = kxio::fs::temp()?;
fs.file_write(
&fs.base().join("file_with_valids.txt"),
include_str!("data/file_with_valids.txt"),
)?;
std::env::set_var("GITHUB_WORKSPACE", fs.base());
std::env::set_var("GITHUB_REPOSITORY", "kemitix/test");
std::env::set_var("GITHUB_SERVER_URL", "https://git.kemitix.net");
//when
let result = run(&TestPrinter::default(), net.clone().into()).await;
let result = run(&TestPrinter::default(), &fs, &net).await;
//then
assert!(result.is_ok()); // there is an invalid file
let requests = net.requests();
assert_eq!(
requests,
vec![SavedRequest::new(
RequestMethod::Get,
"https://git.kemitix.net/api/v1/repos/kemitix/test/issues?state=open",
RequestBody::None,
)]
);
Ok(())
}

View file

@ -1,42 +1,44 @@
use crate::scanner::FileScanner;
//
use super::*;
use std::{cell::RefCell, collections::HashSet, fs::File, io::Write, path::PathBuf};
use std::{cell::RefCell, collections::HashSet, path::PathBuf};
use crate::scanner::FileScanner;
use issues::Issue;
use kxio::net::Net;
use model::Config;
use patterns::issue_pattern;
use pretty_assertions::assert_eq;
use printer::TestPrinter;
#[test]
fn find_markers_in_dir() -> anyhow::Result<()> {
fn find_markers_in_dir() {
//given
let fs = kxio::fs::temp()?;
let fs = kxio::fs::temp().expect("temp fs");
let file_with_invalids = fs.base().join("file_with_invalids.txt");
fs.file_write(
&file_with_invalids,
include_str!("data/file_with_invalids.txt"),
)?;
fs.file_write(
&fs.base().join("file_with_valids.txt"),
include_str!("data/file_with_valids.txt"),
)?;
fs.file(&file_with_invalids)
.write(include_str!("data/file_with_invalids.txt"))
.expect("write with invalids");
fs.file(&fs.base().join("file_with_valids.txt"))
.write(include_str!("data/file_with_valids.txt"))
.expect("write with valids");
let mock_net = kxio::net::mock();
let net = Net::from(mock_net);
let config = Config::builder()
.net(kxio::network::Network::new_mock())
.fs(fs.clone())
.net(&net)
.fs(&fs)
.server("".to_string())
.repo("".to_string())
.issue_pattern(issue_pattern()?)
.issue_pattern(issue_pattern().expect("pattern"))
.build();
let issues = HashSet::from_iter(vec![Issue::new(23), Issue::new(43)]);
let printer = TestPrinter::default();
//when
let errors = find_markers(&printer, &config, issues, &DefaultFileScanner)?;
let errors = find_markers(&printer, &config, issues, &DefaultFileScanner).expect("when");
//then
assert_eq!(
@ -45,37 +47,37 @@ fn find_markers_in_dir() -> anyhow::Result<()> {
"- Issue number missing: file_with_invalids.txt#3:\n It contains a todo comment: // TODO: this is it\n",
"- Issue number missing: file_with_invalids.txt#5:\n It also contains a fix-me comment: // FIXME: and this is it\n",
"- Closed/Invalid Issue: (3) file_with_invalids.txt#9:\n We also have a todo comment: // TODO: (#3) and it has an issue number, but it is closed\n",
format!(">> 3 errors in {}\n", file_with_invalids.strip_prefix(fs.base())?.to_string_lossy()).as_str()
format!(">> 3 errors in {}\n", file_with_invalids.strip_prefix(fs.base()).expect("strip prefix").to_string_lossy()).as_str()
]
);
assert_eq!(errors, 3);
Ok(())
}
#[test]
fn skips_binary_files() -> Result<()> {
fn skips_binary_files() {
//given
let fs = kxio::fs::temp()?;
let fs = kxio::fs::temp().expect("temp fs");
let binary_path = fs.base().join("binary_file.bin");
let mut binary_file = File::create(binary_path)?;
binary_file.write_all(&[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])?;
fs.file(&binary_path)
.write([0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
.expect("write binary file");
let text_path = fs.base().join("text_file.txt");
fs.file_write(&text_path, "text contents")?;
fs.file(&text_path)
.write("text contents")
.expect("write file");
let net = kxio::network::Network::new_mock();
let config = a_config(net, fs)?;
let mock_net = kxio::net::mock();
let net = Net::from(mock_net);
let config = a_config(&net, &fs).expect("config");
let issues = HashSet::new();
let file_scanner = TestFileScanner::default();
let printer = TestPrinter::default();
//when
find_markers(&printer, &config, issues, &file_scanner)?;
find_markers(&printer, &config, issues, &file_scanner).expect("when");
//then
assert_eq!(file_scanner.scanned.take(), vec![text_path]);
Ok(())
}
#[derive(Default)]