Skip to content

Commit

Permalink
Merge pull request #348 from moonbitlang/fuzzy-matching
Browse files Browse the repository at this point in the history
feat: support fuzzy matching for moon test --package
  • Loading branch information
lijunchen authored Sep 27, 2024
2 parents ab6f59e + e6aeecd commit 6cc8cf0
Show file tree
Hide file tree
Showing 24 changed files with 266 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ fs4 = { version = "0.8.3", features = ["sync"] }
ariadne = { version = "0.4.1", features = ["auto-color"] }
clap_complete = { version = "4.5.4" }
schemars = "0.8"
nucleo-matcher = "0.3.1"

[profile.release]
debug = false
Expand Down
71 changes: 70 additions & 1 deletion crates/moon/src/cli/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use moonutil::dirs::PackageDirs;
use moonutil::module::ModuleDB;
use moonutil::mooncakes::sync::AutoSyncFlags;
use moonutil::mooncakes::RegistryConfig;
use moonutil::package::Package;
use n2::trace;
use std::path::Path;
use std::path::PathBuf;
Expand Down Expand Up @@ -210,7 +211,75 @@ fn run_test_internal(
&moonbuild_opt,
)?;

let package_filter = moonbuild_opt.get_package_filter();
let (package_filter, moonbuild_opt) = if let Some(filter_package) = moonbuild_opt
.test_opt
.as_ref()
.and_then(|opt| opt.filter_package.as_ref())
{
let all_packages: indexmap::IndexSet<String> = module
.get_all_packages()
.iter()
.map(|pkg| pkg.0.to_string())
.collect();

let mut final_set = indexmap::IndexSet::new();
for pkg in filter_package {
let needle = pkg.display().to_string();
if all_packages.contains(&needle) {
// exact matching
final_set.insert(needle);
} else {
let xs = moonutil::fuzzy_match::fuzzy_match(&needle, &all_packages);
if let Some(xs) = xs {
final_set.extend(xs);
}
}
}

if let Some(file_filter) = moonbuild_opt
.test_opt
.as_ref()
.and_then(|opt| opt.filter_file.as_ref())
{
let mut find = false;
for pkgname in final_set.iter() {
let pkg = module.get_package_by_name(pkgname);
let files = pkg.get_all_files();
for file in files.iter() {
if file == file_filter {
find = true;
break;
}
}
}
if !find {
eprintln!(
"{}: cannot find file `{}` in package {}, --file only support exact matching",
"Warning".yellow(),
file_filter,
final_set
.iter()
.map(|p| format!("`{}`", p))
.collect::<Vec<String>>()
.join(", "),
);
}
};

let moonbuild_opt = MoonbuildOpt {
test_opt: Some(TestOpt {
filter_package: Some(final_set.clone().into_iter().map(PathBuf::from).collect()),
..moonbuild_opt.test_opt.unwrap()
}),
..moonbuild_opt
};

let package_filter = Some(move |pkg: &Package| final_set.contains(&pkg.full_name()));
(package_filter, moonbuild_opt)
} else {
(None, moonbuild_opt)
};

for (_, pkg) in module.get_filtered_packages_mut(package_filter) {
if pkg.is_third_party || pkg.is_main {
continue;
Expand Down
1 change: 1 addition & 0 deletions crates/moon/tests/cmd_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ fn cmd_test() {
expect_test::expect![[r#"
[
${WORK_DIR}/cond_comp.in/moon.test, ok,
${WORK_DIR}/fuzzy_matching/moon.test, ok,
${WORK_DIR}/moon_build_package.in/moon.test, ok,
${WORK_DIR}/moon_info_001.in/moon.test, ok,
${WORK_DIR}/moon_info_002.in/moon.test, ok,
Expand Down
2 changes: 2 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
.mooncakes/
1 change: 1 addition & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# username/hello
10 changes: 10 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/moon.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "username/hello",
"version": "0.1.0",
"readme": "README.md",
"repository": "",
"license": "",
"keywords": [],
"description": "",
"source": "src"
}
45 changes: 45 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/moon.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
$ moon test -p username/hello/x --sort-input --no-parallelize
x blackbox test
x inline test
x whitebox test
Total tests: 3, passed: 3, failed: 0.

$ moon test -p x --sort-input --no-parallelize
x blackbox test
x inline test
x whitebox test
x_y blackbox test
x_y inline test
x_y whitebox test
Total tests: 6, passed: 6, failed: 0.

$ moon test -p x/y --sort-input --no-parallelize
x_y blackbox test
x_y inline test
x_y whitebox test
Total tests: 3, passed: 3, failed: 0.

$ moon test -p u/h/x/y --sort-input --no-parallelize
x_y blackbox test
x_y inline test
x_y whitebox test
Total tests: 3, passed: 3, failed: 0.

$ moon test -p y --sort-input --no-parallelize
x_y blackbox test
x_y inline test
x_y whitebox test
y blackbox test
y inline test
y whitebox test
Total tests: 6, passed: 6, failed: 0.

$ moon test -p asdf --sort-input --no-parallelize

Warning: no test entry found

$ moon test -p y -f asdf --sort-input --no-parallelize
Total tests: 0, passed: 0, failed: 0.

Warning: cannot find file `asdf` in package `username/hello/x/y`, `username/hello/y`, --file only support exact matching

3 changes: 3 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/src/x/lib.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("x inline test")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/src/x/x_test.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("x blackbox test")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("x whitebox test")
}
3 changes: 3 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/src/x/y/lib.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("x_y inline test")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("x_y blackbox test")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("x_y whitebox test")
}
3 changes: 3 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/src/y/lib.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("y inline test")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions crates/moon/tests/test_cases/fuzzy_matching/src/y/y_test.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("y blackbox test")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test {
println("y whitebox test")
}
1 change: 1 addition & 0 deletions crates/moonutil/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ log.workspace = true
thiserror.workspace = true
schemars.workspace = true
line-index.workspace = true
nucleo-matcher.workspace = true

[dev-dependencies]
expect-test.workspace = true
Expand Down
74 changes: 74 additions & 0 deletions crates/moonutil/src/fuzzy_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// moon: The build system and package manager for MoonBit.
// Copyright (C) 2024 International Digital Economy Academy
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// For inquiries, you can contact us via e-mail at [email protected].

pub fn fuzzy_match<T: AsRef<str>>(
needle: T,
haystack: impl IntoIterator<Item = T>,
) -> Option<Vec<String>> {
let mut matcher = nucleo_matcher::Matcher::new(nucleo_matcher::Config::DEFAULT.match_paths());
let matches = nucleo_matcher::pattern::Pattern::parse(
needle.as_ref(),
nucleo_matcher::pattern::CaseMatching::Ignore,
nucleo_matcher::pattern::Normalization::Smart,
)
.match_list(haystack, &mut matcher);
if matches.is_empty() {
None
} else {
Some(
matches
.into_iter()
.map(|m| m.0.as_ref().to_string())
.collect(),
)
}
}

#[test]
fn test_fuzzy() {
let haystack = [
"moonbitlang/core/builtin",
"moonbitlang/core/int",
"moonbitlang/core/list",
"moonbitlang/core/list/internal",
"moonbitlang/core/hashmap",
];
let result = fuzzy_match("mci", haystack);
expect_test::expect![[r#"
Some(
[
"moonbitlang/core/int",
"moonbitlang/core/list/internal",
"moonbitlang/core/list",
"moonbitlang/core/builtin",
],
)
"#]]
.assert_debug_eq(&result);

let result = fuzzy_match("moonbitlang/core/list", haystack);
expect_test::expect![[r#"
Some(
[
"moonbitlang/core/list",
"moonbitlang/core/list/internal",
],
)
"#]]
.assert_debug_eq(&result);
}
1 change: 1 addition & 0 deletions crates/moonutil/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod common;
pub mod cond_expr;
pub mod dependency;
pub mod dirs;
pub mod fuzzy_match;
pub mod git;
pub mod graph;
pub mod module;
Expand Down
13 changes: 13 additions & 0 deletions crates/moonutil/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ impl Package {
comps.extend(self.rel.components.iter().cloned());
PathComponent { components: comps }
}

pub fn get_all_files(&self) -> Vec<String> {
let mut files =
Vec::with_capacity(self.files.len() + self.test_files.len() + self.wbtest_files.len());
files.extend(
self.files
.keys()
.chain(self.test_files.keys())
.chain(self.wbtest_files.keys())
.map(|x| x.file_name().unwrap().to_str().unwrap().to_string()),
);
files
}
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
Expand Down

0 comments on commit 6cc8cf0

Please sign in to comment.